阿里云服务-米姆科技官方网站 http://www.51mimu.com 广州米姆信息科技有限公司,深度服务阿里云用户,为用户提供产品及技术支持:云服务器ECS、数据库RDS、网站加速CDN、阿里云邮箱、对象存储OSS、阿里云企业邮箱、WAF防火墙、高防IP、企业短信等产品服务,为用户给予阿里云优惠折扣解决方案、阿里云沙龙及阿里云峰会、 免费云主机试用、阿里云促销优惠活动、代金券优惠等活动,让用户享受米姆服务中心阿里云全栈服务。,在线咨询电话:020-87581205 Fri, 20 Jun 2025 02:20:33 +0800 zh-CN hourly 1 https://www.s-cms.cn/?v=4.7.5 公司文化 Fri, 20 Jun 2025 02:20:33 +0800

目前米姆采用人性化管理及制度综合管理相结合的方式让企业管理效率最大化,目前我公司的管理制度,涉及到安全、流程、认证等各方面,各种制度健全合理,包含部分岗位流程SOP、销售服务准则、职能认证规范及要求等十数种程序文件。从各种制度的执行情况来看,各种制度制定的有效合理,能够有效地给予生产进行指导和员工行为规范的约束


   

米姆核心目标:

米姆立志并已致力成为全球领先的企业数字化转型一站式解决方案和营销一体化的服务提供商。价值观:技术创新、诚信合作、以人为本、服务企业员工:员工相互信任、尊重、积极向上,为员工提供更多的培训机会,鼓励员工技术与管理创新、分享知识与经验,为员工提供一个公平公正的可持续发展平台,为客户提供一个搞效率服务平台。
诚信:铸诚魂 弘商誉品质:品质第一 客户至上创新:持续创新 不断超越分享:整合资源 分享世界
]]>
米姆(MEME)科技简介 Fri, 20 Jun 2025 02:20:33 +0800



米姆信息科技专注企业IT服务,技术创新驱动的企业服务专家,主要开展网络安全、信息化建设、公有云服务、私有云建设、中台开发、等保测评、DevOps构建&应用、K8s&容器云搭建、云MSP服务、自动化运维等业务。目前已经在全国服务了超千家企业。


image.png

]]>
测试标题 Fri, 20 Jun 2025 02:20:33 +0800 测试文章内容

]]>
个人清新简洁PPT模板 Fri, 20 Jun 2025 02:20:33 +0800 (46).jpg

]]>
Sodinokibi病毒解析及处置方案 Fri, 20 Jun 2025 02:20:33 +0800

    2021年5月份,多个集团公司遭受了sodinokibi勒索病毒,用户多地集团的终端陆续出现被感染的情况,该病毒导致客户部分业务中断无法正常使用。严重影响了用户的日常工作。在勒索信中提到攻击组织名称REvil,信中除了解密方式,攻击者还提到他将客户重要资料截图发在图片站点https://prnt.sc。


    勒索信提到“我们还从您的服务器下载了大量敏感数据,如果您不付款,我们将开始将您的文件上传到我们的公共博客"图片。通过威胁情报以及对最近2个月内的应急响应案例进行分析,在4月份,其他行业也同样遭受过该类攻击,sodinokibi勒索病毒是继承了GrandCarb的代码结构,然后进行升级勒索手段的方式进行勒索,根据情报该病毒已经席卷了部分知名厂商。


    网络安服团队根据最近的攻击行为分析,本次的攻击手段从原来的钓鱼邮件等方式演变为入侵客户主机后进行直接投放病毒文件的方式进行勒索,定义为APT式勒索软件。结合最近某大型行业的情况,大致判断为有APT攻击组织盯上某大型行业进行勒索攻击。

    如下是网络安服团队对本次病毒事件的具体分析及处置建议。

    

    Sodinokibi勒索病毒分析

    通过被勒索终端排查及样本提取中招主机,排查主机发现勒索样本broker.exe和样本加载脚本start.batstart.bat,在日志内容中发现攻击者从某主机(跳板机)上共享下载并执行勒索样本,该脚本放在组策略启动项中,实现开机关机等操作自启动。推测攻击者通过域控组策略下发勒索病毒加载脚本,从跳板机的共享上下载勒索病毒。broker.exe 式勒索病毒加密本体,采用了白切黑的方式有数字签名,可以实现部分杀软免杀。


    样本入口如下:

image.png


会释放一个exe和一个dll到临时目录,并启动进程MsMpEng.exe。其中MsMpEng.exe主要是调用dll的导出函数ServiceCrtMain

image.png
image.png


导出函数ServiceCrtMain任务是:

image.png


PE如下:

image.png


还原PE标记,使用PE文件解析器可正常解析,但导入表被加密,后来发现病毒手动调用要使用的API(动态解密)


image.png

该PE文件为病毒本体,病毒入口如下:

image.png

该勒索病毒有张配置表,该配置表单主要记录了病毒加密行为以及勒索文本如下:

文件目录避免:

"fld":["$windows.~bt","intel","google","windows","torbrowser","$windows.~ws","application data","mozilla","windows.old","perflogs","appdata","msocache","boot","systemvolume information","program files","program files(x86)","$recycle.bin","programdata"],
文件避免:

"fls":["thumbs.db","bootsect.bak","desktop.ini","ntldr","ntuser.dat","autorun.inf","iconcache.db","boot.ini","bootfont.bin","ntuser.ini","ntuser.dat.log"],
文件扩展名避免:

"ext":["exe","mod","shs","cpl","idx","diagcfg","ico","nomedia","sys","cmd","key","msp","msstyles","bin","rom","bat","cur","diagcab","ldf","dll","scr","hta","rtp","hlp","theme","msi","com","prf","spl","wpx","deskthemepack","diagpkg","mpa","icns","ps1","drv","ics","nls","adv","msu","cab","lnk","ocx","ani","themepack","icl","msc","386","lock"]},
文件目录移除:

"wfld":["backup"]
杀死进程清单:

"prc":["mydesktopqos","thebat","synctime","onenote","mspub","dbsnmp","isqlplussvc","tbirdconfig","oracle","xfssvccon","wordpad","agntsvc","sqbcoreservice","ocautoupds","firefox","msaccess","thunderbird","excel","outlook","encsvc","visio","powerpnt","ocomm","steam","mydesktopservice","ocssd","sql","winword","dbeng50","infopath"]
杀死服务清单:

"svc":["veeam","sql","svc$","backup","sophos","vss","memtas","mepocs"]

勒索文本:

[+] Whats Happen? [+]
Your files are encrypted, and currently unavailable. You can check it: all files on your system has extension u89416xh.
By the way, everything is possible to recover (restore), but you need to follow our instructions. Otherwise, you cant return your data (NEVER).
[+] What guarantees? [+]

......................................


并且病毒会判断所感染计算机使用的语言,如下:

红框中为目标感染国家的语言ID,如果使用的函数GetUserDefaultUILanguage,GetSystemDefaultUILanguage返回的ID和列表框中的ID相同,那么为感染目标,通过此处来看修改非目标计算机语言可避免感染该病毒image.png

病毒会创建互斥体确保唯一运行,病毒会多次检查自己的句柄权限是否为管理员权限,如果权限不够将会重新以管理员权限启动自己,并且激活相关权限。病毒实际的行为是在Sub_F4476F_Start函数中,如下:

image.png

病毒首先清空回收站,然后设置注册表自启动,关闭清单中的相关服务,杀死清单中进程,然后在激活相关权限的情况下,开始加密功能。主要使用kernel32_FindFirstFile 和kernel32_FindNextFile来查找所有文件,使用salsa20+AES的算法进行文件加密加密

image.png

在加密的过程如果发现文件为目标感染文件,但被进程占用,病毒会调用terminateProcesss结束相关进程,然后再加密

image.png

加密函数如下:

image.png


病毒也会同时对网络磁盘中的文件进行加密

image.png

在加密的过程中病毒有枚举网络资源的行为,疑似内网传播及拷贝,如下:

image.png

在加密功能完成以后会调用函数Sub_F458E2设置桌面背景为勒索图片

image.png



Sodinokibi病毒处置方案



1.应急措施

阻断各个接口的网络连接。全网发出安全通告,通知服务器、PC断网、未开机电脑禁止开机,断网线


2.处置建议

第一,Sodinokibi 勒索病毒暂无解密工具,可先将被加密的重要文件、勒索信息文件备份保存,以待将来有解密工具时解密


第二,病毒样本可通过终端EDR以及防病毒客户端进行监测与查杀


第三,部署态势感知类设备以及全流量探针对整网的安全情况进行分析、监测,通过流量分析并找出其他可能感染勒索病毒的机器


同时,针对本次Sodinokibi勒索病毒,米姆网络安服团队的防护建议如下:


【日常运维建议】

第一,通过日常的风险评估对现有的安全环境进行检查,定期对资产与网络进行风险评估,确保漏洞、威胁能得到妥善解决


第二,加强企业员工安全意识培训,不轻易打开陌生邮件或运行来历不明的程序


【安全防护建议】

第一,及时更新操作系统及其他应用的高危漏洞安全补丁,对所有服务器、个人终端推送勒索病毒利用的ms17-010漏洞补丁进行安装加固,补丁链接如下:https://www.catalog.update.microsoft.com/Search.aspx?q=ms17-010


第二,加强对域控的安全防护:检测是否存在ZeroLogon(CVE-2020-1472)漏洞,如有通过以下链接获取补丁进行加固:https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-1472


第三,尽量避免危险端口对外开放,利用 IPS、防火墙等设备对危险端口进行防护(445、139、3389 等)


第四,开启 windows 系统防火墙,通过 ACL 等方式,对 RDP 及 SMB 服务访问进行加固


第五,通过 Windows 组策略配置账户锁定策略,对短时间内连续登陆失败的账户进行锁定


第六,加强主机账户口令复杂度及修改周期管理,并尽量避免出现通用或规律口令的情况;


第七,修改系统管理员默认用户名,避免使用 admin、administrator、test 等常见用户名


第八,安装具备自保护的防病毒软件,防止被黑客退出或结束进程,并及时更新病毒库


第九,部署EDR类的终端检测防护类设备,针对勒索病毒攻击性行为进行发现与拦截


【修复建议】

定时对重要业务数据进行备份,防止数据破坏或丢失


【安全监测建议】

建议部署态势感知类设备以及全流量探针对整网的安全情况进行分析、监测,并配合威胁情报完善日常安全工作


网络安全专项服务


对企业安全仍有担忧?针对本次勒索病毒,米姆网络安服团队可提供以下专项服务:

上门病毒排查服务;

针对性的基线配置检测;

试用企业级防病毒系统;

专业的企业级灾备方案。


全方位保障企业安全,除了以上服务,米姆网络还提供基础安全服务、现场安全评估服务、应用安全评估服务、风险评估服务、咨询服务、培训服务、一站式等保服务、驻点服务等多项安全服务。


处置思路


修复本地系统:

安装MS17-010补丁,修补SMBv1服务漏洞。

恢复网络拦截能力:

用满足当前环境的流量监控分析设备,拦截一部分的攻击数据包,并记录内部攻击源。

增加本地安全能力:

激活全部终端的防病毒客户端,修改特权账号,修复弱密码。
















]]>
阿里云双十一优惠活动怎么玩? Fri, 20 Jun 2025 02:20:33 +0800 官网首页图.png

官网首页图.png

参与时间活动主题活动方式活动对象活动内容使用时间专属链接说明
10月24日-11月11日开宝箱领取满减代金券阿里云所有用户满50减7.5
    满300减45
    满700减105
    满1000减150
    满2000减240
    满3500减420
    满5000减600
    满8000减960
    1、同一账号仅可参与一次开宝箱;
    2、每个订单仅可用一次满减代金券。可与3折以上折扣产品和购物车满减叠加(
3折需要验证
代金券使用时间:
    11月1日-11月11日
https://www.aliyun.com/1111/home?spm=5176.19720258.J_2937333540.5.7b652c4ab8sFAa代金券不适用产品:
   
安全产品:内容安全/漏洞扫描,数据库审计、堡垒机、实人认证、应急响应、等保咨询、威胁狩猎服务、安全加固、安全咨询服务、内容安全基础包、内容安全语音/文本加油包、内容安全并发套餐;
    CDN&视频云、云通信、IoT全线产品;
    云服务总线、应用实时监控服务、容器服务、消息队列 MQ、容器服务Kubernetes版、容器镜像服务、应用配置管理;
    云小蜜、云呼叫中心、智能对话分析;
    域名、商标服务、软件著作权登记、虚机、云市场全线产品;
11月1日-抽上云红包抽奖得红包个人红包面值:5/20/30/66元
    企业红包面值88/166/188/288/1111元
    1、指定云产品的
新购、升级订单,每个订单仅可使用1个红包;可与指定云产品折扣、购物车满减优惠叠加
    2、
可多次使用,直到抵扣完毕
11月1日-11月11日红包不适用产品:
   
活动价小于300元的云服务器产品;视频云、云通信、IoT全部产品;漏洞扫描按次、漏扫包年包月、内容安全包年包/加油包、内容安全语音及文本包;容器镜像ACR、边缘容器服务ACK;域名、商标服务、软件著作权登记、虚机;
11月1日-11月13日拼团活动
1、无门槛开团,通过拼团链接每完成一笔新购订单,团队随机获得一个拼团红包,最高奖励11111元;升级续费均不计入有效;
    2、同一个账号仅可开一个团
    3、自推自卖的拼团订单无法交易,不算入有效。
红包使用时间:
    11月1日-11月30日
https://www.aliyun.com/1111/pintuan-share?ptCode=MTgwNDc2NDgyNTM1OTU3NXx8MTE0fDE%3D&userCode=lojocax7云大使享有额外佣金加码机会
11月1日-11月11日充值返券
充2000返满1000减100代金券*2
    充6000返满3000减300代金券*2
    充2万返满1万减1500代金券*2
    充5万返满2万5减5000代金券*2
    可购买今年7月-11月期间未保有产品
11月9日-11月11日
区分同人账号
10月24日-11月30日电销账号满减电销申请激活码给用户归属电销的客户满3500减350;
    满12000减1200;
    满25000减2500;
    满50000减5000;
    满100000减10000;
    满150000减15000;
    满200000减20000;
11月9日-11月11日/1、仅用于产品新购、升级订单,购买2种以上产品可用(不同产品)、5折以下不可参与满减(5折参与),订单时长≤1年,每个用户仅可享受1次;
   
2、不参与产品清单:漏洞扫描按次、漏扫包年包月、内容安全包年包/加油包、内容安全语音及文本包、云企业网、全球加速、爆款-CDN/全站加速资源包10TB、50TB-6个月、CDN/全站加速资源包1PB/5PB   下行流量、爆款-点播资源包10TB、50TB、爆款-视频直播流量包10TB、50TB、短信(非1年期产品)、OCR印刷文字识别、ocr、函数计算fc、serverless应用引擎sae、微服务引擎MSE、企业级分布式应用服务   EDAS 3.0、边缘容器服务ACK@Edge(月)、边缘容器服务ACK@Edge(叠加3个月)、消息队列rocketmq、消息队列kafka、IOT全类目、域名、知识产权首购、智能LOGO设计、商标注册提货券
11月1日-11月11日新人专区通过指定购买页面采购指定配置产品新用户ECS低至0.73折
    数据库、安全产品低至
3折
    云通信低至
0.72折
11月1日-11月11日https://www.aliyun.com/1111/new?spm=5176.20584151.J_2721338800.9.7f1b449fPlitjk
   
可与上云红包叠加
11月1日-11月11日爆款专区不限指定配置11月1日-11月11日https://www.aliyun.com/1111/home?utm_content=se_1007171491&accounttraceid=3711e73c185e408fa44eab862620ad35ggqs可与上云红包叠加

产品开始时间活动主题活动方式活动对象活动条件使用时间折扣专属链接
数据库10月28日
领取数据库首购代金券数据库新客满500减200
    满1000减400
    满2000减800
    首购数据库产品可用
11月1日-11月11日60%

领取数据库新购升级代金券
满1000减50
    满2000减100
    满4000减200
    满10000减500元
    满20000减1000元
    满40000减2000元
    新购升级指定产品可用
95%
网络11月1日
领取新购升级通用代金券
满300减10
   
满1000减50




领取云企业网/全球加速新购升级代金券
满1000减50
    满5000减350


安全11月1日
1、领取100元无门槛代金券(5折及以下不适用)
    2、购物车满减
(包1年新购)

每10W减1W,最高减10万11月1日-11月11日
http://www.aliyun.com/1111/security
ECS11月1日
分会场领取满减代金券
满1000减120
    满3000减360
    满10000元减1200
11月9日-11月11日


消费满1500元,可抽奖一次
10台1年ECS免费使用权
    250张续费升级代金券



活动时间客户限制产品折扣备注
11月1日-11月11日新客T5/S6/C6a/轻量应用服务器S6最低0.73折每人限购一台,S6每天限量4000台
企业新客C5G5C6G6C6eG6e
    R6hfc6hfg6G6aR6a
半年4.5折、1年4.1折及3年3.8折限购3台
老客GPU主要是gn5、gn6i
    其他:C6e/G6e/hfc6/hfc7/C6/G6/R6/C5/G5/R5
GPU
    1个月5.5折
    半年:8.5折
    1年:8折
每人限购1台






活动主题产品活动时间活动内容

首续折扣云服务器ECS截止2021.3.31首购用户首次续费一年享受7折优惠
    详细请见https://www.aliyun.com/daily-act/ecs/activity_selection
    首次购买ECS用户第一次续费


购物车-首复满减
首购用户首次复购2000减200,封顶减30000
    详细请见https://www.aliyun.com/daily-act/ecs/activity_selection
    首次购买ECS的用户再次购买新的ECS


升配折扣截止2020.11.30实例升级到2-8核享受6.5折优惠
    带宽升级到5-6M享受6.5折优惠
    云盘扩容至半年以上200G,可享受6.5折优惠
    同一用户仅限参与1次,仅限1台实例参与升级;
    2020年7月31日前购买且保有1-2台ECS实例的用户
    本活动同一用户仅限参与1次,仅限1台实例参与升级


购物车-复购满减(暗线)截止2020.11.30满2000减1000(少量满1500减500)首购用户第一次复购
    详细请见https://www.aliyun.com/daily-act/ecs/activity_selection


折扣-续费折扣截止2020.12.31续费半年享受7.5折优惠
    续费一年享受
6.5折优惠
    详细请见https://www.aliyun.com/daily-act/ecs/care
    本活动同一用户仅限
参与1次仅限1台实例参与升级,首购用户第一次续费


活动时间产品订单类型配置版本存储空间客户限制时长原价折扣折后价每人数量限制备注
11月1日-11月11日Mysql新购2核4G基础版100GB新客1年307030%9211台
新购2核4G高可用100GB企业新客1年696030%20881台
新购4核8G高可用100GB企业新客1年1236030%37081台
新购4核8G三节点100GB企业新客1年1656030%49681台
新购8核16G三节点100GB企业新客1年2880030%86401台
Polar   DB新购4核8G单节点
新客1年345630%1036.81台
新购计算包 小型800 CU内

新客1年3000030%90001个
新购4核16G两节点
企业新客1年2400030%72001台
DBS新购small

新客1年168030%5041台
新购Medium

企业新客1年268830%806.41台
11月9日-11月11日Mysql新购2核4G基础版200G内老用户1年427270%2990.4不限
新购2核4G高可用200G内老用户1年792080%6336不限
新购4核8G基础版200G内老用户1年678070%4746不限
新购4核8G高可用200G内老用户1年1332080%10656不限
新购8核16G高可用200G内老用户1年2352080%18816不限
新购1:8规格

老用户1年4092050%20460不限
新购三节点版

老用户1年4968050%24840不限
SQLServer新购2核4G标准版200G内老用户1年1026070%7182不限
新购4核8G标准版200G内老用户1年1668070%11676不限
新购8和16G标准版200G内老用户1年2952070%20664不限
新购2核4GWEB版200G内老用户1年624070%4368不限
新购4核8GWEB版200G内老用户1年948070%6636不限
新购8和16GWEB版200G内老用户1年1620070%11340不限
新购2核4G企业版200G内老用户1年1080070%7560不限
新购4核8G企业版200G内老用户1年1860070%13020不限
新购8和16G企业版200G内老用户1年3360070%23520不限
PostgreSQl新购2核4G基础版200G内老用户1年427270%2990.4不限
新购4核8G基础版200G内老用户1年678070%4746不限
新购4核8G高可用200G内老用户1年1702870%11919.6不限
新购8和16G高可用200G内老用户1年3004870%21033.6不限
Polar DB-M新购计算包 800CU内

老用户1年3040050%15200不限

新购4核16G双节点
老用户1年2400080%19200不限

新购4核8G单节点
老用户1年345680%2764.8不限
Polar DB-M存储包新购1TB内

老用户1年3780080%30240不限
Polar DB-O新购4核16G

老用户1年3120070%21840不限
Polar DB-X新购16核64G

老用户1年5586070%39102不限
Redis新购云盘版1G

老用户1年108050%540不限
新购云盘版2G

老用户1年216050%1080不限
新购云盘版4G

老用户1年432050%2160不限
新购云盘版8G

老用户1年864050%4320不限
新购本地盘版1G

老用户1年120080%960不限
新购本地盘版2G

老用户1年216080%1728不限
新购本地盘版4G

老用户1年408080%3264不限
新购本地盘版8G

老用户1年792080%6336不限
MongDB新购4.4版本

老用户1年900050%4500不限
新购2核4G
200G内老用户1年1258680%10068.8不限
新购4核8G
400G内老用户1年2395280%19161.6不限
HBase新购4核16G
1TB老用户1年5558480%44467.2不限
新购8核32G
1TB老用户1年8716880%69734.4不限
ADB新购C8
500G老用户1年62937.680%50350.08不限
MyBase新购全规格

老用户1年56803.3480%45442.67不限
RDS升级全规格

老用户不限
70%

按量转包年也可,只读实例也可
Redis升级全规格

老用户不限
70%


MongoDB升级全规格

老用户不限
70%


Polar DB升级全规格

老用户不限
70%


HBase升级全规格

老用户不限
70%


ADB升级全规格

老用户不限
70%


Mybase
32核

老用户1年85126.5420%16888首购限制前20位,每人限制2-4台
ADB
32核2节点
老用户1年6522014%8888首购限制前20位,每人限制2-4台
PolarDB 计算包
2000CU

老用户1年7600022%16888首购限制前20位,每人限制2-4台
活动产品订单类型订单时长折扣申请条件截止时间
数据库全线产品新购升级1个月95折

3个月9折

6个月85折

1年8折

1年75折订单总额5万/年
1年7折订单总额15万/年






活动产品订单门槛续费代金券备注
数据库产品续费20001001.有且仅有5个面值
    2.同一个客户一个面值档位可以领一张。 
    3.同一个客户每一个续费订单可以用一张。如果需要使用多张券,可以分多个续费订单。
    4.11月9日0点到11日24点间使用,过期作废。

5000250
10000500
200001000
500002500
活动时间订单类型客户限制产品指定配置折扣力度备注
11月1日-11月11日新购不限漏洞扫描包年/按次3折
内容安全语音及文本专项包3折
实人认证智能核身20万次35折
实人认证/金融级实人认证流量包50万次5折
内容安全基础包/加油包1000万次及以上5折
SSL证书vTrus DV通配符6折
云安全中心(态势感知)开通防篡改功能7折
云防火墙高级版7折
敏感数据防护包月,全规格7折
DDOS防护包月,全规格85折
WEB应用防火墙包月,全规格85折







1、新购安全10万以上且无大客户折扣,可申请暗线
    2、续费升级,不限订单金额,可申请暗线
    3、改价&特殊套餐所有折扣均不享受。
    注:部分产品不参与暗线,需提前沟通
明线活动时间订单类型客户限制产品指定配置时长限制网络满减代金券是否叠加官网满减代金券是否叠加上云红包充返代金券官网折扣力度暗线折扣力度暗线活动时间备注
11月1日-11月11日预付费新客负载均衡简约型I、标准型I1年××1年:实例2折,带宽75折(>5M)
11月1日-11月30日
预付费新客3月××3月:实例25折,带宽8折

预付费新客NAT网关小型1年××30%

预付费新客3月××35%

预付费新客弹性公网IP<50M1年××75%

预付费新客3月××80%

预付费新客共享流量包不限
85%全规格83折1个月10G没有折扣
预付费新客云企业网2-20M(跨境/不跨境)1年××55%

预付费新客1月××60%
跨境限企业用户
预付费新客全球加速实例:选型I+小型II
    基础/跨境带宽包:2-20M
1年××实例:小型I1%;小型II25折
    基础/跨境带宽包:55折
带宽51折起
预付费新客1月××实例:小型I1%;小型II3折
    基础/跨境带宽包:6折
预付费新客VPN网关IPSec-5M/10M1年实例
    IPSec-5M:3折
    IPSec-10M:65折
    带宽:75折


预付费新客3月实例
    IPSec-5M:35折
    IPSec-10M:7折
    带宽:8折


预付费新客智能接入网关SAG-APP(5-30个客户端)1年5-10个客户端:3折
    10-30个客户端:65折


预付费新客3月5-10个客户端:35折
    10-30个客户端7折


11月9日-11月11日新购升级老客负载均衡不限1年

实例:7折
    带宽:8折
带宽:75折(>5M)
老客NAT网关不限1年

65%75折(>5M)
老客弹性公网IP不限1年

80%75折(>5M)
老客共享流量包不限不限

85%83%
老客云企业网2-20M(跨境/不跨境)1年

70%6折起
老客1月

75%6折起
老客全球加速实例:选型I+小型II
    基础/跨境带宽包:2-20M
1年

70%51折起
老客1月

75%51折起
老客VPN网关不限1年

实例:7折
    带宽:8折
70%
老客智能接入网关SAG-APP,所有规格1年

70%65%
活动时间订单类型客户限制产品指定配置明线折扣暗线折扣暗线折后价备注
11月1日-11月11日新购新客性能测试PTS1W vum按量付费资源包10%
0.99起不与其他优惠共享
新购不限10W   vum/2周起
    (包周包月资源包)
85%
4980起
新购新客应用高可用AHAS70节点*天起
    (流量防护资源包)
50%
99起
新购不限应用高可用AHAS500节点*天起
    (流量防护资源包)
50%
1375起
新购新客函数计算FC1CU预购计算力50%
41.05起
新购serverless应用引擎SAE5000核*时/月,10000G*时/月90%
810起
新购微服务引擎MSE预付费全规格
    (包月资源包)
90%
45.14起
新购EDAS 3.0预付费标准版,5个实例/月
    (5个实例起购,包月资源包)
80%
180起
新购EDAS 3.0预付费专业版,1个实例/月
    (包月资源包)
75%
112.5起
新购消息队列kafka预付费全规格(限购一个月)75%
1292.625起
新购消息队列Rocket MQ预付费全规格75%
202.5
新购容器镜像ACR EE(月)基础版、标准版、高级版75%70%546起不与其他优惠共享
新购容器镜像ACR EE(叠加3个月)基础版、标准版、高级版68%63%1474.2起不与其他优惠共享
新购容器镜像ACR EE(年)基础版、标准版、高级版65%60%5616起不与其他优惠共享
新购不限应用监控服务ARMS150探针*天(应用监控资源包)10%
70起不与其他优惠共享
新购200万页面上报次数(前端监控资源包)85%
42起不与其他优惠共享
新购消息队列rocketMQ预付费年包(不退款)85%
2754不与其他优惠共享
新购消息队列kafka预付费半年包(不退款)90%
8550.9不与其他优惠共享
新购消息队列rocketMQ预付费半年包(不退款)90%
1458不与其他优惠共享
新购消息队列kafka预付费年包(不退款)85%
16151.7不与其他优惠共享
活动时间订单类型客户限制产品指定配置时长明线折扣秒杀价(1折)首购价(3折)暗线折扣暗线备注备注
11月1日-11月30日新购新客OSS存储包500G3月1折起16.249

每天上午10点限量50个秒杀,不叠加其他优惠

1TB33.399.9

新购文件存储NAS极速型300G1月48.6146

新购混合云备份存储容量500G13.3240

新购存储容量单位包500G3折50150

新购日志服务存储包1TB1折起32.25697

新购表格存储存储包1TB21.564.6

新购升级老客OSS存储包全规格不限折上8折
   
(相当于原价6折)


折上7折预付费(新购、升级、续费)折上折可多次使用
新购升级文件存储NAS

新购升级混合云备份HBR

新购升级存储容量单位包

新购升级日志服务SLS

新购升级表格存储OTS




新购不限混合云存储入门级阵列SA2100双控32G/4TB   NLSAS*3/1G*8接口
6折



秒杀,限50台
新购混合云存储基础级阵列SA2600双控64G/8TB   NLSAS*5/1G*8接口,可扩展多协议接口
56折



秒杀,限3台
新购除秒杀配置外
大额代金券

伙伴折扣
暗线可叠加5千元代金券
新购混合云存储主流级阵列SA3700双控128G/960G   SSD*4/1G*8接口,已扩展为10G ISCSI(SFP+)
58折



秒杀,限3台
新购除秒杀配置外
大额代金券

伙伴折扣
暗线可叠加1万元代金券

活动时间客户限制产品指定配置折扣备注
11月1日-不限OCR印刷文字识别
15%
新客PAI交互式建模P10030%首月新购600元/月
MC-Hologres32核128G
首月888元
Elastisearch4核16G以下50%首购年付5折
DataWorks专业版4%首月新购199元
MaxCompute预付费标准计算资源1.3%首购首月10cu/199元
Databricks数据洞察指定规格0.86%首月新购599元
Quick BI标准版30%首购

高级版65%首购
实时计算Flink全托管版本10cu/99元0.29%首购首月10cu/99元
11月9日-11月11日老客PAI交互式建模P10040%包月仅需800元,单笔订单最多买6个月
PAI在线预测服务
80%升级任一规格
MC-Hologres
80%计算配置限时升级
开放搜索共享通用版70%年付
智能推荐标准版70%年付
实时计算Flink独享模式85%年付
Quick   BI标准版45%复购
高级版70%复购

新人用户

类别实例CPU/内存系统盘带宽购买时长活动价原价折扣说明备注
ECS共享型S61核2G高效云盘40G1M1年84.97523.80.16
个人用户
1M3年254.921326.960.19
个人用户
2核4G高效云盘40-100G1-10M1年226.08847.80.27
个人用户
1-10M3年508.682147.760.24
个人用户
2核8G高效云盘40-100G5-10M1年2449.923253.80.75
个人用户
5-10M3年7349.7676410.96
个人用户
4核8G高效云盘40-100G1-10M3个月598.328310.72
个人用户
1-10M1年731.282825.40.26
个人用户
1-10M3年2193.845707.80.38
个人用户
轻量应用服务器1核1GSSD 40G
1年125.49690.13
个人用户
1核2G
1年191.414790.13
个人用户
2核4GSSD 60G
3个月592.87800.76
个人用户
2核8GSSD 80G
3个月75610800.70
个人用户
突发性能t51核2G高效云盘40-100G
1年96.996.91.00
个人用户

3年290.7290.71.00
个人用户











持续更新
      活动链接:https://www.aliyun.com/1111/new?spm=5176.20584151.J_2721338800.9.7f1b449fPlitjk
   

拼团产品

拼团链接:https://www.aliyun.com/1111/pintuan-share?ptCode=MTgwNDc2NDgyNTM1OTU3NXx8MTE0fDE%3D&userCode=lojocax7
用户类别实例CPU/内存系统盘带宽购买时长活动价原价折扣说明备注
新用户ECS共享型S61核2G高效云盘40G1M1年84.97523.80.16

1M3年254.921326.960.19

2核4G高效云盘40-100G3M1年295.211070.27

3M3年664.22804.40.24

4核8G高效云盘40-100G5M1年1000.563865.80.26

5M3年3001.6888290.34

计算型C52核4G高效云盘40-100G5M半年874.819440.45企业用户带宽可选
计算型C6e8核16G高效云盘40-100G5M1年2575.089516.60.27企业用户带宽可选
5M3年7725.2419787.40.39企业用户带宽可选
轻量应用服务器1核1GSSD 40G
1年125.49690.13

1核2G
1年191.414790.13

老用户ECS计算型C62核4G40-100G3-10M1年2668.82835.60.94
带宽、云盘可选
4核8G1年446447430.94
带宽、云盘可选
计算型C52核4G40-100G3-10M1年259227540.94
带宽、云盘可选
4核8G1年4310.44579.80.94
带宽、云盘可选
RDSMySQL2核4G高可用版
1年633667320.94

4核8G
1年10656113220.94

8核16G
1年18816199920.94

1:8规格

1年10860152040.71
全规格五折
三节点版

1年504060480.83
全规格五折
Redis
1G云盘版
1年5409180.59
1年5折

2G
1年108018360.59
1年5折

4G
1年216036720.59
1年5折

8G
1年432073440.59
1年5折
国内短信包5000条


2年2252500.90

1.5W条


2年6357050.90

云安全中心高级版


1年91810800.85

CDN全站加速流量半年包1TB


半年90
0.5
限购5个
5TB


半年450
0.5
限购5个
企业邮箱5账号


1年5406000.90
3年7折


]]>
【升级】10月微消息队列MQTT升级公告 Fri, 20 Jun 2025 02:20:33 +0800

【阿里云】【微消息队列MQTT】【升级通知】

升级窗口:

北京时间2020年10月12日 23:00 - 2020年10月13日 07:00

北京时间2020年10月14日 23:00 - 2020年10月15日 07:00

北京时间2020年10月19日 23:00 - 2020年10月20日 07:00

北京时间2020年10月21日 23:00 - 2020年10月22日 07:00

北京时间2020年10月26日 23:00 - 2020年10月27日 07:00

北京时间2020年10月28日 23:00 - 2020年10月29日 07:00

升级内容:所有地域的MQTT服务。

升级影响:

升级期间MQTT控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端应用需要设置自动重连,以免影响业务。

升级期间,消息发送可能会有少量失败,应用做好断连失败重试机制;同时可能会有消息延迟的现象。如需在控制台进行管理操作,请避开维护时间段。

给您带来的不便敬请谅解,有任何问题,可随时通过工单联系反馈。

]]>
【升级】10月消息服务MNS升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

【阿里云】【消息服务MNS】【升级通知】
升级窗口:

北京时间2020年10月15日 00:00 - 06:00

北京时间2020年10月22日 00:00 - 06:00

北京时间2020年10月29日 00:00 - 06:00

升级内容:华北1(青岛)、华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、华东2金融云、华南1金融云、华北2政务云、香港、亚太东南1(新加坡)、亚太东南2(悉尼)、亚太东南5(雅加达)、亚太南部1(孟买)、中东东部1(迪拜)、欧洲中部1(法兰克福)、美国东部1(弗吉尼亚)、美国西部1(硅谷)、英国(伦敦)等地域的消息服务升级。
升级影响:升级期间MNS相关服务及控制台访问可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

给您带来的不便敬请谅解,有任何问题,可随时通过工单或服务电话95187联系反馈。

]]>
【升级】10月21日消息队列AMQP升级通知 (更新) Fri, 20 Jun 2025 02:20:33 +0800

【阿里云】【消息队列AMQP】【升级通知】

升级窗口:(已更新)北京时间2020年10月21日 00:00 - 03:00
升级内容:华北1(青岛)、华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、香港等全部地域(及铂金版)的服务升级。
升级影响:升级期间消息队列AMQP相关服务访问可能会出现多次闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过 5 分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

]]>
【升级】10月17日CNNIC注册局系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

【阿里云】【域名】【注册局维护通知】

维护时间:北京时间2020年10月17日 08:00 - 22:00

维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

维护影响:届时 .cn/.中国 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

1、您提交的域名注册、续费、转入、赎回、一口价域名购买等业务在支付费用后状态为“处理中”,待维护结束后将变为相应的处理结果和状态。

2、维护过程中,您无法对域名注册信息进行修改,将提示修改失败。

3、维护过程中,您无法下载相关域名的域名证书,将提示下载失败。

4、维护过程中,.cn/.中国域名实名认证不能正常提交至注册局,将为“审核中”状态,待维护结束后按顺序提交。

如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

由此给您带来的不便,我们表示歉意,敬请谅解。

]]>
【升级】10月21日Datahub公有云2.18版本产品稳定性升级通知 Fri, 20 Jun 2025 02:20:33 +0800

【阿里云】【Datahub】【产品稳定性升级】

升级窗口:北京时间2020年10月21日 10:00 - 19:00

升级内容:

1. 公有云HTTPS证书更新

2. 版本统一升级到2.18版本

升级区域:华北,华东,华南,新加坡,吉隆坡,孟买,德国

升级影响:在升级过程中,有服务短暂抖动重试,属于正常现象,其他如有任何问题,可点击联系我们进行咨询反馈。

给您带来的不便敬请谅解。

]]>
【漏洞预警】Apache Solr configset upload文件上传漏洞(CVE-2020-13957) Fri, 20 Jun 2025 02:20:33 +0800

2020年10月13日,阿里云应急响应中心监测到Apache Solr发布安全更新,其中修复了CVE-2020-13957 Apache Solr configset upload文件上传漏洞。攻击者通过构造特定的请求,成功利用该漏洞可直接获取服务器权限。


漏洞描述

Apache Solr是一个开源的搜索服务,使用Java语言开发。Apache Solr Configset Api上传功能存在未授权漏洞。在特定条件下,攻击者可以构造特定请求,上传相关恶意文件,从而直接获取到服务器权限。阿里云应急响应中心提醒Solr用户尽快采取安全措施阻止漏洞攻击。


影响版本

Apache Solr 6.6.0 - 6.6.5

Apache Solr 7.0.0 - 7.7.3

Apache Solr 8.0.0 - 8.6.2


安全版本

Apache Solr 8.6.3


安全建议

1. 升级至安全版本

2. 如果未使用ConfigSets API,请禁用UPLOAD命令,将系统属性: configset.upload.enabled 为 false ,可参考官方文档:https://lucene.apache.org/solr/guide/8_6/configsets-api.html

3. 增加身份验证/授权,可参考官方文档:https://lucene.apache.org/solr/guide/8_6/authentication-and-authorization-plugins.html

4. 使用在SOLR-14663中提到的补丁程序:https://issues.apache.org/jira/browse/SOLR-146634

5. 禁止Solr API 以及管理 UI 直接对公网开放。设置防火墙,以便只允许受信任的计算机和人员访问。


相关链接

https://issues.apache.org/jira/browse/SOLR-14925

https://issues.apache.org/jira/browse/SOLR-14663



阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测

阿里云云防火墙已可防御此漏洞攻击


我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.13

]]>
【漏洞预警】Windows TCP/IP远程执行代码漏洞(CVE-2020-16898) Fri, 20 Jun 2025 02:20:33 +0800

2020年10月13日,阿里云应急响应中心监测到微软发布补丁修复了TCP/IP远程执行代码漏洞(CVE-2020-16898),官方评级严重。目前微软官方已提供相应的月度安全补丁以修复该漏洞。


漏洞描述

微软官方于10月13日发布安全更新,其中修复了一个TCP/IP远程执行代码漏洞(CVE-2020-16898),攻击者通过构造并发送恶意的ICMPv6(路由通告)数据包,从而控制目标主机。同时,微软10月补丁中还涉及其他多个高危漏洞,阿里云应急响应中心提醒 Windows 用户尽快安装补丁阻止漏洞攻击。


漏洞评级

CVE-2020-16898 严重


影响版本

Windows Server 2019

Windows Server 2019 (Server Core installation)

Windows Server, version 1903 (Server Core installation)

Windows Server, version 1909 (Server Core installation)

Windows Server, version 2004 (Server Core installation)


安全建议

1、前往微软官方下载相应补丁进行更新:https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16898

2、阿里云云安全中心Windows系统漏洞模块已支持对该漏洞补丁一键检测和修复,详情登陆云安全中心

Windows Server 2019 补丁:KB4577668



3、可以通过禁用ICMPv6 RDNSS来缓解风险。

使用以下PowerShell命令禁用ICMPv6 RDNSS,以防止攻击者利用此漏洞。此解决方法仅适用于Windows 1709及更高版本。

netsh int ipv6 set int *INTERFACENUMBER* rabaseddnsconfig=disable


注意:进行更改后,无需重新启动。

可以使用以下PowerShell命令禁用上述缓解方法。

netsh int ipv6 set int *INTERFACENUMBER* rabaseddnsconfig=enable


注意:禁用替代方法后,无需重新启动。


相关链接

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16898



我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.13

]]>
【漏洞预警】VMware vCenter任意文件读取漏洞 Fri, 20 Jun 2025 02:20:33 +0800

2020年10月14日,阿里云应急响应中心监测到VMware vCenter特定版本存在任意文件读取漏洞,攻击者通过构造特定的请求,可以读取服务器上任意文件。


漏洞描述

VMware vCenter 服务器是一种高级服务器管理软件,提供一个用于控制 VMware vSphere 环境的集中式平台。VMware vCenter特定版本存在任意文件读取漏洞,攻击者通过构造特定的请求,可以读取服务器上任意文件。阿里云应急响应中心提醒VMware vCenter用户尽快采取安全措施阻止漏洞攻击。


已知影响版本

VMware vCenter 6.5.0a-f


安全版本

VMware vCenter 6.5u1


安全建议

升级至安全版本


相关链接

https://www.vmware.com/products/vcenter-server.html



阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.14

]]>
【漏洞预警】Nexus Repository Manger 2&3 Shiro身份验证绕过漏洞 Fri, 20 Jun 2025 02:20:33 +0800

2020年10月15日,阿里云应急响应中心监测到 sonatype官方 发布了 Nexus Repository Manger 2&3 Shiro验证绕过漏洞。


漏洞描述

Sonatype Nexus Repository 是一个开源的仓库管理系统,在安装、配置、使用简单的基础上提供了更加丰富的功能。近日Sonatype官方发布安全公告披露了在Nexus Repository Manager 2 & 3 版本中使用了旧版本的Shiro组件,存在权限绕过漏洞。攻击者可利用该权限绕过漏洞访问到后台功能,并可能导致命令执行。阿里云应急响应中心提醒Nexus Repository Manager 2&3用户尽快采取安全措施阻止漏洞攻击。


影响版本

Nexus Repository Manager OSS/Pro version 2.x < 2.14.19

Nexus Repository Manager OSS/Pro version 3.x < 3.27.0


安全版本

Nexus Repository Manager 2 versions 2.14.19

Nexus Repository Manager 3 versions 3.27.0


安全建议

1. 升级至安全版本

2. 该漏洞检测请参考 【漏洞预警】Apache Shiro < 1.6.0 权限绕过漏洞(CVE-2020-13933)


相关链接

https://support.sonatype.com/hc/en-us/articles/360053556313-CVE-2020-13933-Nexus-Repository-Manger-2-3-Shiro-Authentication-Bypass


阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.15

]]>
【漏洞预警】Adobe Magento 远程代码执行漏洞(CVE-2020-24407) Fri, 20 Jun 2025 02:20:33 +0800

2020年10月19日,阿里云应急响应中心监测到 Adobe官方发布了 CVE-2020-24407 Magento 远程代码执行漏洞通告。


漏洞描述

Magento是一套专业开源的电子商务系统。近日Adobe官方发布安全公告披露了在 Magento Commerce/Open Source 2.3以及2.4版本中存在CVE-2020-24407远程代码执行、CVE-2020-24400 SQL注入等多个漏洞。在具有管理特权的情况下,攻击者可构造恶意请求,绕过文件上传限制,从而造成远程代码执行,控制服务器。阿里云应急响应中心提醒Magento用户尽快采取安全措施阻止漏洞攻击。


影响版本

Magento Commerce/Open Source <= 2.3.5-p2

Magento Commerce/Open Source <= 2.4.0

Magento Commerce/Open Source <= 2.3.5-p1


安全版本

Magento Commerce/Open Source 2.4.1

Magento Commerce/Open Source 2.3.6


安全建议

升级至安全版本


相关链接

https://helpx.adobe.com/security/products/magento/apsb20-59.html



我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.19

]]>
【漏洞预警】Apache Kylin API未授权访问漏洞(CVE-2020-13937) Fri, 20 Jun 2025 02:20:33 +0800

2020年10月20日,阿里云应急响应中心监测到 Apache Kylin官方修复 CVE-2020-13937 API未授权访问漏洞。


漏洞描述

Apache Kylin™是一个开源的、分布式的分析型数据仓库。近日Apache Kylin官方修复 CVE-2020-13937 API未授权访问漏洞。攻击者可构造恶意请求,访问API地址,可以获取Apache Kylin的相关配置信息,从而导致身份凭证等信息泄漏。阿里云应急响应中心提醒 Apache Kylin 用户尽快采取安全措施阻止漏洞攻击。


影响版本

Kylin 2.x.x

Kylin <= 3.1.0

Kylin 4.0.0-alpha


安全版本

Kylin 3.1.1


安全建议

升级至安全版本


相关链接

https://www.mail-archive.com/dev@kylin.apache.org/msg12170.html



我们会关注后续进展,请随时关注官方公告。

如有任何问题,可随时通过工单或服务电话95187联系反馈。

阿里云应急响应中心

2020.10.20

]]>
3分钟短文:太爽了,用Laravel写API接口!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 引言

我们一直在讲,通过路由传达到控制器,处理好数据并渲染到视图,但是对于现代的应用,
前后端分离的情况下,后端写个接口就完事儿了。

img

本期为大家说一说用laravel写restful风格的API,看看能有多简单。

以路由开端

写API接口,与传统的渲染前端模板页面有什么区别?少了视图,只需要准备好数据,
并按照规则格式化,返回就可以了。

laravel默认的api接口路由在 routes/api.php 文件内定义,默认的情况下预定义了一个资源类型的api接口,代码如下:

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

调用了 auth:api 中间件用于验证用户的授权,如果授权通过,声明的get方法获取用户的信息,并返回 User 模型。这在之前的章节是很常见的操作,我们不做赘述了。

那么这个路由文件,是什么时候加载上去的呢?在文件 app/Providers/RouteServiceProvider.php 内,看这样一段:

protected function mapApiRoutes()
{
    Route::prefix('api')
        ->middleware('api')
        ->namespace($this->namespace)
        ->group(base_path('routes/api.php'));
}

该服务提供者声明路由使用 api 字符前缀,并调用 api 中间件,该中间件定义在 app/Http/Kernel.php 文件内:

protected $middlewareGroups = [
    'api' => [
        'throttle:60,1',
        IlluminateRoutingMiddlewareSubstituteBindings::class,
    ],
];

至于命名空间 $this->namespace 一般返回 AppHttpControllers,我们为了区分API与其他应用,在目录 app/Http/Controller 下创建 API 目录,用于存储所有API相关的控制器。

那么上述的 RouteServiceProvider.php 文件内 mapApiRoutes 方法内的 namespace 需要这样写:

->namespace($this->namespace . 'API')

仍然以 Event 模型作为示例,在 routes/api.php 文件内声明一个资源类型的路由:

Route::resource('/events', 'APIEventsController');

注意命名空间上多出来的前缀 API ,这说明我们是把 EventController 文件放在了 API 目录下。

用户权限

让我们把目光还聚焦在系统默认声明的那条路由:

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

注意中间件 auth:api,因为api请求是无状态的,每次请求之间没有任何关联,所以使用用户权限区分资源的返回。那么我们怎么拿到用户授权呢?这在 config/auth.php 文件内定义,看系统自带的这一段配置代码:

'guards' => [
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
],

这一段定义了我们使用何种方式认证用户的身份。默认的驱动 token 定义在框架文件 laravel/framework/src/Illuminate/Auth/TokenGuard.php 内。长话短说,默认构造类传入的字段如下:

UserProvider $provider,
Request $request,
$inputKey = 'api_token',
$storageKey = 'api_token',
$hash = false

简单说,就是使用 users 表的 api_token 字段用户鉴权。那么默认我们 users 表显然缺少一个这样的字段,现在使用迁移文件补上:

php artisan make:migration add_api_token_field_to_users_table --table=users

首先是迁移方法 up 函数:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('api_token', 60)->unique();
    });
}

还有回滚使用的 down 方法:

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('api_token');
    });
}

这些都是常规操作,我们在之前的章节,使用了N多次了。执行指令迁移数据库:

php artisan migrate

看看效果

准备好了路由,而且路由内声明了一个get方法返回用户模型数据。也准备好了数据库表字段 api_token。我们在数据库表内找到一个用户数据,把api_token值设置为 1234,用于测试。

现在在浏览器内请求类似如下的url地址:

http://www.example.com/api/user?api_token=1234

如无异常,顺利会输出一个 json 字符串,

{
    "id":1,
    "provider":null,
    "provider_id":null,
    "first_name":"Tom",
    "last_name":"Hanks",
    "email":"tom@admin.com",
    "city":"",
    "state_id":null,
    "zip":"43016",
    "lat":null,"lng":null,
    "timezone":"America/New_York",
    "title":"Laravel Developer",
    "created_at":"2020-10-14 17:46:19",
    "updated_at":"2020-10-14 17:46:20",
    "last_login_at":null,
    "is_admin":0,
    "api_token":"1234"
}

这个json格式的数据是怎么来的呢?是在路由内,$request->user() 方法返回的User模型,使用 toArray() 格式化方法获得的。为了演示,很多字段与实际可能有所出入。

特别需要注意的是,关键的密码字段,以及 token 字段,是默认隐藏的,这得益于 User 模型内 $hiden 属性的定义:

protected $hidden = [
    'password', 'remember_token',
];

这些字段都对对外不公开访问。

写在最后

本文介绍了如何声明api地址,已经解释了api从中间件到路由的由来,明白了api授权的方式,可以为我们更灵活地定制授权方式提供便利。这在laravel内都是可插拔的,替换为我们的逻辑代码就可以愉快工作了。

Happy coding :-)

我是@程序员小助手,专注编程知识,圈子动态的IT领域原创作者

]]>
面对复杂业务,if-else coder 如何升级?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1.png

作者 | 张建飞  阿里巴巴高级技术专家

导读:针对业务在不同场景下的差异,我们常常会习惯性地使用 if-else 来实现不同的业务逻辑,久而久之代码越来越难以维护。那么如何消除这些 if-else?面对复杂业务应如何思考和分析?本文分享阿里高级技术专家张建飞(Frank)关于复杂业务治理的方法论,介绍一种多维度分析问题的方法:矩阵分析法。

You should not be a if-else coder, should be a complexity conquer. 
——Frank

这篇文章,是对之前我在《阿里高级技术专家方法论:如何写复杂业务代码?》说的“自上而下的结构化分解 + 自下而上的抽象建模”方法论的升级。因为在之前的方法论中,我们缺少一个多维度看问题的视角,这种维度思维的缺失,可能会导致 miss 掉一些重要的业务信息,从而使我们制定软件设计策略的时候,陷入困难。

有了维度思维,我们便可以更加方面的去看清业务的全貌,更加全面的掌握业务信息,从而帮助我们更加体系化的去治理复杂性。

从 if-else 说起

我经常说,我们不要做一个 if-else coder。这里的 if-else,不是说我们在 coding 的时候不能使用 if-else,而是说我们不应该简陋地用 if-else 去实现业务的分支流程,因为这样随意的代码堆砌很容易堆出一座座“屎山”。

业务的差异性是 if-else 的根源。以零售通的商品业务为例。不同的处理场景,其业务逻辑实现是有差异性的。如下图所示,商品业务的差异性,主要体现在商品类型、销售方式和仓储方式的不同。

2.png

这三个维度上的差异组合起来,有 2 3 2 = 12 之多。这就是为什么在老代码中,到处可以看到 if(组合品) blabla,if(赠品) blabla,if(实仓) blabla 之类的代码。

那么,要如何消除这些讨厌的 if-else 呢?我们可以考虑以下两种方式:

  • 多态扩展:利用面向对象的多态特性,实现代码的复用和扩展。
  • 代码分离:对不同的场景,使用不同的流程代码实现。这样很清晰,但是可维护性不好。

1. 多态扩展

多态扩展可以有继承和组合两种方式。继承勿用多言,组合有点像策略模式,也就是把需要扩展的部分封装、抽象成需要被组合的对象,然后对其进行扩展,比如星环的能力扩展点就是这种方式。

这里,我们举一个继承的例子,商品在上架的时候要检查商品的状态是否可售,普通商品(Item)检查自己就好了,而组合商品(CombineItem)需要检查每一个子商品。

用过程式编码的方式,很容易就能写出如下的代码:

public void checkSellable(Item item){
    if (item.isNormal()){
        item.isSellable(); 
        //省略异常处理
    }
    else{
        List<Item> childItems = getChildItems();
        childItems.forEach(childItem -> childItem.isSellable()); 
        //省略异常处理
    }
}

然而,这个实现不优雅,不满足 OCP,也缺少业务语义显性化的表达。更好的做法是,我们可以把 CombineItem 和 Item 的关系通过模型显性化的表达出来。

3.png

这样一来,一方面模型正确的反应了实体关系,更清晰了。另一方面,我们可以利用多态来处理CombineItem和Item的差异,扩展性更好。重构后,代码会变成:

public void checkSellable(Item item){
    if (!item.isSellable()){
        throw new BizException("商品的状态不可售,不能上架");
    }
}

2. 代码分离

所谓的代码分离是指,对于不同的业务场景,我们用不同的编排代码将他们分开。以商品上架为例,我们可以这样写:

/**
* 1. 普通商品上架
*/
public void itemOnSale(){
    checkItemStock();//检查库存
    checkItemSellable();//检查可售状态
    checkItemPurchaseLimit();//检查限购
    checkItemFreight();//检查运费
    checkItemCommission();//检查佣金
    checkItemActivityConflict();//检查活动冲突

    generateCspuGroupNo();//生成单品组号
    publishItem();//发布商品
}

/**
* 2. 组合商品上架
*/
public void combineItemOnSale(){
    checkCombineItemStock();//检查库存
    checkCombineItemSellable();//检查可售状态
    checkCombineItemPurchaseLimit();//检查限购
    checkCombineItemFreight();//检查运费
    checkCombineItemCommission();//检查佣金
    checkCombineItemActivityConflict();//检查活动冲突

    generateCspuGroupNo();//生成单品组号
    publishCombineItem();//发布商品
}

/**
* 3. 赠品上架
*/
public void giftItemOnSale(){
    checkGiftItemSellable();//检查可售状态
    publishGiftItem();//发布商品
}

这种方式,当然也可以消除 if-else,彼此独立,也还清晰。但复用性是个问题。

3. 多维分析

细心的你可能已经发现了,在上面的案例中,普通商品和组合商品的业务流程基本是一样的。如果采用两套编排代码,有点冗余,这种重复将不利于后期代码的维护,会出现散弹式修改(一个业务逻辑要修改多处)的问题。

一个极端情况是,假如普通商品和组合商品,只有 checkSellable() 不一样,其它都一样。那毫无疑问,我们使用有多态(继承关系)的 CombineItem 和 Item 来处理差异,会更加合适。

而赠品上架的情况恰恰相反,它和其他商品的上架流程差异很大。反而不适合和他们合用一套流程代码,因为这样反而会增加他人的理解成本。还不如单独起一个流程来的清晰。

那么,问题来了,我们什么时候要用多态来处理差异,什么时候要用代码分离来处理差异呢?

接下来,是我今天要给你着重介绍的多维度分析问题的方法论之一:矩阵分析法。

我们可以弄一个矩阵,纵列代表业务场景,横列代表业务动作,里面的内容代表在这个业务场景下的业务动作的详细业务流程。对于我们的商品业务,我们可以得到如下的矩阵:

4.png

通过上面的矩阵分析,我们不难看出普通品和组合品可以复用同一套流程编排代码,而赠品和出清品的业务相对简单,更适合有一套独立的编排代码,这样的代码结构会更容易理解。

维度思维

1. 多维度的重要性

上面的案例不是我编造出来的,而是我在和张文(我同事)讨论应该用哪种方式去处理业务差异的真实故事。

我记得在和大学讨论完,开车回去的路上,我一直在想这个问题,然后在第二个路口等红灯的时候,突然有一个灵感冒出来。我抑制不住兴奋,一边开车,一边发消息给张文说:“我想到了一个很 NB 的方法论,能解决在‘多态扩展’和‘代码分离’之间如何做选择的问题”。

其实,我知道我兴奋的不仅仅是解决了这个问题。我兴奋的是,我第一次真正领悟到了多维度思考的重要性。从而有机会从一个“单维度”生物,升级成一个“多维度”思考者。妈妈再也不用担心我被“降维打击”了 :)

结构化思维有用、很有用、非常有用,只是它更多关注的是单向维度的事情。比如我要拆解业务流程,我要分解老板给我的工作安排,我要梳理测试用例,都是单向维度的。

而复杂性,通常不仅仅是一个维度上的复杂,而是在多个维度上的交叉复杂性。当问题涉及的要素比较多,彼此关联关系很复杂的时候,两个维度肯定会比一个维度要来的清晰,这也是为什么说矩阵思维是比结构化思维更高层次的思维方式。

实际上,我们从汉语的词汇上,也不难看出一个人的思维层级,是和他的思考维度正相关的。当我们说这个人很“轴”、“一根筋”的时候,实际上是在说他只有一维的线性思维。所以,观察事物的视角越多,维度越丰富,其思维层级也会越高。

5.png

2. 无处不在的多维思考

有了这些感悟,我开始系统的整理关于多维度思考分析的资料,发现这种思考方式真是无处不在。发现的越多,我越是感慨,为什么如此重要的思维方式,我到现在才领悟到。

1)波士顿矩阵

比如,在做产品分析的时候,有对产品发展前景进行分析的波士顿矩阵。

6.png

2)订单要素分析

当年,我在 1688 做交易下单业务的时候,有非常多的下单场景,每种场景下,买家享受的权益是不一样的(如下表所示)。我们当时也是使用了矩阵去表达这个复杂的关系,只是当时还没有想到要将其提升到方法论的高度。

7.png

3)数据交叉分析

在数据分析中,维度分析是非常重要的,特别是维度很多的时候,我们可以通过皮尔逊积矩相关系数,做交叉分析,从而弥补独立维度分析没法发现的一些问题。

8.png
简单相关系数矩阵

4)分析矩阵

最近我碰巧看到 Alan Shalloway 写的《设计模式解析:Design Patterns Explained》,这是一本非常经典的关于 OOP 的书,里面的第十六章就是专门讲“分析矩阵”的,作者创造这个方法论的初衷也是因为业务涉及的要素太多,信息量太大,他需要一种组织海量数据的新方式。

9.png

我和 Alan 的路径不一样,但是都得出了同样的结论。由此可见,这种矩阵分析的方式的确是对复杂业务进行分析的一把利器,业务场景越多,交叉关系越是复杂,越需要这样的分析。

5)组织阵型

生产关系决定生产力,对于一个管理者来说,如何有效的设置组织结构是决定团队是否能高效协作的关键。所以我们可以看到公司里面,每年都有比较大的关于组织结构和人员安排的调整。

对于技术团队来说,我们习惯于按领域划分工作范围,这样做的好处是责任到人、职责清晰。然而,领域只是一个维度,我们工作通常都是以项目的形式的开展,而项目通常是贯穿多个领域的。所以,在做团队组织规划的时候,我们可以通过业务领域和业务项目两个维度去看。

比如,在我负责的商品团队,我会按照如下的形式去做职责划分。

10.png

6)时间维度

除了工作,生活中也到处可见多维思考的重要性。

比如,我们说浪费可耻,应该把盘子舔的很干净,岂不知加上时间维度之后,你当前的舔盘,后面可能要耗费更多的资源和精力去减肥,反而会造成更大的浪费。

我们说代码写的丑陋,是因为要“快速”支撑业务,加上时间维度之后,这种临时的妥协,换来的是意想不到的 bug,线上故障,以及无止尽的 996。

7)RFM 模型

简单的思考是“点”状的,比如舔盘、代码堆砌就是当下的“点”;好一点的思考是“线”状,加上时间线之后,不难看出“点”是有问题的;再全面一些的思考是“面”(二维);更体系化的思考是“体”(三维);比如,RFM 模型就是一个很不错的三维模型。可惜的是,在表达上,我们人类只能在二维的空间里去模拟三维,否则四维可能会更加有用。

11.png

复杂业务治理总结

在前言部分,我已经说过了,多维分析是对之前方法论的升级。加上以前的方法论,完整的方法论应该是“业务理解-->领域建模-->流程分解-->多维分析”。

为了方便大家理解,下面我把这些方法论做一个简单的串联和解释。

1. 业务理解

理解业务是所有工作的起点。首先,我们要找到业务的核心要素,理解核心概念,梳理业务流程。

比如,在零售通的商品域,我们要知道什么是商品(Item),什么是单品(CSPU),什么是组合品(CombineItem)。在下单域,我们要知道订单(order)的构成要素是商品、优惠、支付。在 CRM 领域,我们要理解客户、机会、联系人、Leads 等等。

这里,我想再次强调下语言的重要性,语言是我们思考的载体,就像维特根斯坦说的:“凡是能够说的事情,都能够说清楚”。

你不应该放过任何一个模糊的业务概念,一定要透彻的理解它,并给与合理的命名(Ubiquitous Language)。唯有如此,我们才能更加清晰的理解业务,才能更好的开展后续的工作。

2. 领域建模

在软件设计中,模型是指实体,以及实体之间的联系,这里需要我们具备良好的抽象能力。能够透过庞杂的表象,找到事务的本质核心。

再复杂的业务领域,其核心概念都不应该太复杂,抓住了核心,我们就抓住了主线,业务往往都是围绕着这些核心实体展开的。

比如,商品域虽然很复杂,但其核心的领域模型,无外乎就如下图所示:

12.png

3. 流程分解

关于流程分解,在《阿里高级技术专家方法论:如何写复杂业务代码?》里面已经有非常详细的阐述,这里就不赘述了。

简单来说,流程分解就是对业务过程进行详细的分解,使用结构化的方法论(先演绎、后归纳),最后形成一个金字塔结构。

比如,在商品领域,有创建商品、商品上架、上架审核、商品下架、下架审核、修改商品、删除商品等一些列动作(流程),每个动作的背后都有非常复杂的业务逻辑。我们需要对这些流程进行详细的梳理,然后按步骤进行分解。最后形成一个如下的金字塔结构:

13.png

4. 多维分析

关于多维分析,我以二维的矩阵分析为例,我想我前面应该已经说清楚了。

业务的复杂性主要体现在流程的复杂性和多维度要素相互关联、依赖关系上,结构化思维可以帮我们梳理流程,而矩阵思维可以帮忙我们梳理、呈现多维度关联、依赖关系。二者结合,可以更加全面的展现复杂业务的全貌。从而让我们的治理可以有的放矢、有章可循。

既然是方法论,在这里,我会尝试给出一个矩阵分析的框架。试想下,如果我们的业务很简单,只有一个业务场景,没有分支流程。我们的系统不会太复杂。之所以复杂,是因为各种业务场景互相叠加、依赖、影响。

因此,我们在做矩阵分析的时候,纵轴可以选择使用业务场景,横轴是备选维度,可以是受场景影响的业务流程(如文章中的商品流程矩阵图),也可以是受场景影响的业务属性(如文章中的订单组成要素矩阵图),或者任何其它不同性质的“东西”。

14.png

通过矩阵图,可以清晰的展现不同场景下,业务的差异性。基于此,我们可以定制满足差异性的最佳实现策略,可能是多态扩展,可能是分离的代码,也可能是其它。

这就是矩阵分析的要义,其本质是一种多维度思考的方法论。

篇后寄语

最后,我想说世界是熵增的(即万物都在缓慢的分崩离析),控制复杂度是我们这些从业者无法推卸的责任和使命。

软件行业的发展才几十年,还是一门年轻的学科,软件工程就像一个刚学会走路的小孩,还很不成熟,有时还很幼稚。

但毕竟还是有几十年的沉淀,还是有一些好的方法和实践可以参考,我的这些总结沉淀只是在前人的基础上,多走了一点点而已。但就是这一点点,也实属来自不易,其中冷暖,只有自己能体会。可以说,这一路走来,是一场对心力、脑力和体力的持续考验。

15.png

  • 心力是指不将就的匠心,不妥协的决心,不满足的好奇心、以及不放弃的恒心。
  • 脑力是指那些必要的思维能力、学习能力、思考能力、思辨能力。
  • 之所以说“业务理解-->领域建模-->流程分解-->多维分析”是体力,是因为实现它们就像是在做填空题,只要你愿意花时间,再复杂的业务都可以按部就班的清晰起来。

梳理清晰了,再配合 COLA(https://start.aliyun.com/)的指导,我们就有可能写出清晰、易读的代码,就有可能从一个 if-else coder 升级为一个 complexity conquer。

而这不正是我们工程师孜孜不倦的追求吗?

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

]]>
阿里云rds并发性能解读-大分区表高并发性能提升100倍?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 阿里云某客户发现自己使用读写分离实例,master的cpu特别高,而读写分离中承担读流量的slave节点却相对空闲。用户CPU打满后,访问到主节点的的线上服务受到了较大影响。关于阿里云RDS请参考云数据库RDS详情链接

Redis读写分离实例的原理是:key统一写入到master,然后通过主从复制同步到slave,用户的请求通过proxy做判断,如果是写请求,转发到master;如果是读请求,分散转发到slave,这种架构适合读请求数量远大于写请求数量的业务,读写分离架构示意图如下所示。
1.png
阿里云Redis读写分离版读写命令转发示例

bitfield命令

经过和客户沟通查看后,客户使用了大量的bitfield做读取,首先介绍一下这个命令的用法和场景,bitfield 是针对bitmap数据类型操作的命令,bitmap通常被用来在极小空间消耗下通过位的运算(AND/OR/XOR/NOT)实现对状态的判断,常见的使用场景例如:

  • 通过bitmap来记录用户每天应用登录状态,即如果$ID用户登录,就SETBIT logins:20200404 $ID 1,表示用户$ID在20200404这一天登录了,通过BITCOUNT logins:20200404可以得到这一天所有登录过的用户数量;通过对两天的记录求AND,可以判断哪个用户连续两天登录了,即BITOP AND logins:20200404-05 logins:20200404 logins:20200404。
    判断用户是否阅读了共同的文章,观看了共同的视频等。

前一阵子,答题领奖活动非常火爆,“答对12道题的同学有机会瓜分奖池”,这种如果使用bitmap来实现,就非常容易判断出用户是否全部答对。

1.png
一个使用Redis BITMAP设计的答题游戏系统

答题系统设计如:

  • 每个用户每轮答题,设置一个key,比如user1在第一轮答题的key是 round:1:user1
    每答对一道题,设置相关的bit为1,比如user1答对了第5题,那么就设置第5个bit为1就可以了,如: SETBIT round:1:user1 5 1 ;如果用户1在第一轮答对了第9题,那么就把第9个bit设置为1,SETBIT round:1:user1 9 1;值得注意的是,bitfield默认bit都是0,答错可以不设置

计算用户总共答对了几道题,就可以使用 BITCOUNT 命令统计1的bit个数。如user1答对了3道题,user2在第一轮全部答对,那么user2就有机会参与答题(第1轮)的后续玩法

可见,Redis的bitmap接口可以用非常高的存储效率和计算加速效果。回到bitfiled命令,它的语法如下所示:

BITFIELD key  
[GET type offset] // 获取指定位的值
[SET type offset value] // 设置指定位的值
[INCRBY type offset increment] // 增加指定位的值
[OVERFLOW WRAP|SAT|FAIL] // 控制INCR的界限

读写分离实例处理bitfield的问题

从上文可知,bitfield的子命令中,GET命令是读属性,SET/INCRBY命令为写属性,因此Redis将其归类为写属性,从而只能被转发到master实例,如下图所示为bitfield的路由情况。

1.png

这就是为什么客户使用了读写分离版,而只有master节点cpu使用高,其余slave节点却没有收到这个命令的打散的原因。

解决方案

  • 方案一:改造Redis内核,将bitfield命令属性标记为读属性,但是当其包含SET/INCRBY等写属性的子命令时候,仍旧将其同步到slave等。此方案优点是外部组件(proxy和客户端)不需要做修改,缺点是需要对bitfiled命令做特殊处理,破坏引擎命令统一处理的一致性。
    方案二:增加bitfield_ro命令,类似于georadius_ro命令,用来只支持get选项,从而作为读属性,这样就避免了slave无法读取的问题。此方案优点是方案清晰可靠,缺点是需要proxy和客户端做适配才能使用。

经过讨论,最终采取了方案二,因为这个方案更优雅,也更标准化。

添加bitfield_ro

{"bitfield_ro",bitfieldroCommand,-2,
"read-only fast @bitmap",
0,NULL,1,1,1,0,0,0},

完成之后,下图是在slave上执行bitfield_ro命令,可以看到被正确执行。

tair-redis > SLAVEOF 127.0.0.1 6379
OK
tair-redis > set k v
(error) READONLY You can't write against a read only replica.
tair-redis > BITFIELD mykey GET u4 0
(error) READONLY You can't write against a read only replica.
tair-redis > BITFIELD_RO mykey GET u4 0
1) (integer) 0

Proxy转发

为了保持用户不做代码修改,我们在proxy上对bitfiled命令做了兼容,即如果用户的bitfield命令只有get选项,proxy会将此命令转换为bitfield_ro分散转发到后端多个节点上,从而实现加速,用户不用做任何改造即可完成加速,如下图所示。

1.png

添加BITFIELD_RO命令后处理BITFIELD逻辑流程

贡献社区

我们将自己的修改回馈给了社区,并且被Redis官方接受

值得一提的是,阿里云在国内是最大的Redis社区contributer,如在新发布的Redis-6.0rc中,阿里云的贡献排第三,仅次于作者和Redis vendor(Redis Labs)。阿里云仍旧在不断的回馈和贡献社区。

阿里云Redis通过增加bitfield_ro命令,解决了官方bitfield get命令无法在slave上加速执行的问题。

除过bitfield命令,阿里云Redis也同时对georadius命令做了兼容转换,即在读写分离实例上,如果georadius/georadiusbymember命令没有store/storedist选项,将会被自动判断为读命令转发到slave加速执行。

我们思考读写分离版的场景,为什么用户需要读写分离呢?为什么不是用集群版呢?我们做一下简单对比,比如设置社区版的服务能力为K,那么表的对比如下(我们只添加了增强版Tair的主备做对比,集群版可以直接乘以分片数):

方式 Redis社区版集群 Redis社区版读写分离 Redis(Tair增强版)主备
写(key均匀情况) K分片数 K K3
读(key均匀情况) K分片数 K只读节点数 K*3
写(单key或热key) K(最坏情况) K K*3
读(单key或热key) K(最坏情况) K只读节点数 K3
​表1. Redis社区版(集群/读写分离)和增强版(主备)简单场景对比

可见,其实读写分离版属于对单个key和热key的读能力的扩展的一种方法,比较适合中小用户有大key的情况,它无法解决用户的突发写的瓶颈,比如在这个场景下,如果用户的bitfield命令是写请求(子命令中带有INCRBY和SET),就会遇到无法解决的性能问题。

从表的对比看,这种情况下,用户如果能把key拆散,或者把大key拆成很多小key,就可以使用集群版获得良好的线性加速能力。大key带来的问题包含但不仅限于:

大key会造成数据倾斜,使得Redis的容量和服务能力不能线性扩展
大key意味着大概率这个key是热点
一旦不小心针对大key有range类的操作,会出现慢查询,还容易打爆带宽
这也是Tair增强版在阿里集团内各个应用建议的:“避免设计出大key和慢查,能避免90%以上的Redis问题”。

但是在实际使用中,用户仍旧不可避免的遇到热点问题,比如抢购,比如热剧,比如超大型直播间等;尤其是很多热点具备“突发性”的特点,事先并不知晓,冲击随时可达。Redis增强版的性能增强实例具备单key在O(1)操作40~45w ops的服务能力和极强的抗冲击能力,单机主备版就足够应对一场中大型的秒杀活动!同时如果用户没有大key,增强性能集群版能够近乎赋予用户千万甚至几千万OPS的服务能力,这也是Tair作为阿里重器,支持每次平稳渡过双11购物节秒杀的关键

原文链接:https://www.9i0i.com/article-95490-1.html

]]>
全面上云这条路,洋葱学院已经走了近7年 -阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 洋葱学院在2013年末成立,原名洋葱数学,是一家K12在线教育公司。课程里没有真人老师授课,而是采用100%人机交互学习方式,每节课5-8分钟动画视频的形式精讲一个知识点或解题思维,希望有趣生动的讲解方式让更多的孩子们爱上学习。

创新且锐意进取,是这个公司的基因。

洋葱学院由杨临风、朱若辰和李诺联合创办,团队希望通过技术方式促进教育均衡。在创立之初,团队就做出了一个意识超前的决定:整套业务系统均基于阿里云搭建。
image.png
要知道,2013年,能选择全面上云的中国企业屈指可数。

上云先锋洋葱学院这一路走来,也是和阿里云一同携手成长的难忘历程。几年前,由于某个特殊使用场景,线上业务受到严重影响,洋葱学院和阿里云紧急沟通、快速排查问题,阿里云云数据库的专家同学进行了重启、备份等一系列工作。

早先,洋葱学院起步于初中数学、物理课程,但是随着不断发展,开始加入语文和英语等学科,这些课程特点不同、相应的学习流程不同。除了早先选用的ECS云服务、SLB负载均衡、阿里云云数据库等经典基础产品之外,洋葱学院也尝试将新的业务应用搭建于一些新兴阿里云产品,如ACK容器服务、RSS弹性伸缩、SLS日志服务、Blink实时计算、ARMS前端监控等,来满足日趋复杂和多样化的业务需求。

如今,从初中理科逐渐扩展到小初高全学段全学科,为130多万教师以及3600多万的中小学生提供24小时在线的“云课堂”。

疫情大考突如其来,IT架构稳如泰山

疫情期间(近一个月以来),有超过700万学生、35万教师使用洋葱学院APP在线学习或辅助授课,同时还将课程资源开放给学习强国、快手等第三方平台播放,帮助更广泛的学生远程学习。

能应对猝不及防的疫情流量洪峰,洋葱都做了怎样的努力、下足了哪些功夫呢?

在线教育业务的一个重要特点,就是波峰波谷比较规律,可预测。在学校下课或放假时期,业务会达到上升,洋葱学院便会在数分钟扩容云上资源,待学生返校上课之时,再根据业务情况释放资源,这样持续保持较高的资源利用率,既节省成本又确保业务响应。

3年前,洋葱学院开始尝试微服务改造,将复杂的单体架构进行拆分和解耦。同时采用容器技术,并也将swarm迁移至阿里云容器服务ACK之上,原本每个模块都对应一套ECS与SLB,但是随着微服务越拆越细,开始出现资源浪费的情况,而且调度复杂度都在迅速膨胀。容器服务可以根据不同模块的配置所需,资源分配更加合理,按照定义规则自动弹性伸缩避免了复杂的调度维护。

image.png

容器的弹性

基本功夯实的基础上,洋葱学院还做了一些方案优化和升级。

延期开学的这段时间里,广大学生学习时间较为集中,面对大流量、高并发访问需求,洋葱学院需要确保业务稳定性,采用阿里云容器服务与云数据库融合解决方案,在应用不变的情况下,快速平稳实现扩容的问题。阿里云容器服务可以在几分钟内扩充底层资源,满足快速部署数千个应用实例的需求。阿里云容器服务团队的建议下,洋葱学院还进一步优化了整体的ECS服务器配置,将大量的小规格ECS服务器更换成30至50核大规格ECS,从容应对10倍扩容,同时运维管控更加便捷。

针对疫情延期开学,洋葱学院作为头部K12在线教育公司,免费向全国师生开放了平台的全部核心课程资源,这期间每天的学习访问人数持续飙升。使用云容器之后,系统在资源利用率上提升了约60%,出现问题后可快速隔离,当面对急剧增长的业务量,也可以在短时间内扩容进行业务支撑。——李诺 洋葱学院联合创始人&CTO

image.png

数据库的升级

为了确保平台使用起来“丝般顺滑”,阿里云为洋葱学院提供了综合架构解决方案:数据库层将云数据库Redis数据库做高速缓存,RDS PostgreSQL+MongoDB做持久化存储;应用层对微服务进行改造,以及容器化部署。这是完成挑战的核心能力。

洋葱学院还对冷用户和冷热数据做了优化:冷用户,即第一次来的用户信息较少,此期间冷用户过多,亟需优化冷用户的流程处理,这需要提高高压下的数据快速处理能力,处理变得更快;老师和学生的作业数据,会有冷热数据之分,每隔一段时间进行数据迁移,但是热数据增长过快,此前方案逐渐应接不暇。在阿里云云数据专家的建议下,将冷用户缓存增加,升级数据库,对数据库进行了分库分表,还进行了一系列索引优化、语句改写以及业务改造等工作。

在此次疫情中,洋葱学院利用阿里云数据库的极致弹性、无缝升级扩容能力,一晚上便完成了几十个核心数据库的容量升级以及PG实例版本升级。单个云Redis集群可承载千万级访问的超高性能,确保了即使流量数十倍增长也不会有业务瓶颈。同时,持久化存储RDS PostgreSQL、MongoDB有更强的承载能力,不仅可以应对复杂查询,还可做到极致超强弹性水平扩展,全面保障了洋葱学院的运行,在业务量比历史同期翻了10倍的情况下仍然保持平稳。

故障检测

此外,原本只能依靠负载均衡的定时扫描错误节点,故障检测存在一定时延;而Kubernetes自带容灾和错误发现机制,容器内部pod之间自动实现切换,大大缩短问题发现时间,同时基于阿里云云监控、ARMS Prometheus、ARMS前端监控和日志服务,实现云资源、容器集群、容器节点、Pod等指标的完善监控,对集群变更状态、pod创建拉起删除、组件异常等信息,基本可以覆盖到各种监控报警问题,将重大故障‘扼杀于摇篮之中’。

AI辅助教学,学生老师个性化“Friday”

image.png
看过复仇者联盟的朋友们,都记得钢铁侠的AI助手Friday,每次关键时刻都协助钢铁侠力挽狂澜。

洋葱学院的APP,其实早已经成为很多学生和老师的AI助手Friday。2017年,洋葱学院成立人工智能实验室,并尝试将AI赋能引入其教学体系。最主要的原因,是希望为学生们打造完整的学习闭环,产品能根据学生的学习现状和效果,动态规划学习路径,推送个性化学习内容。

而老师则可以通过数据后台实时充分掌握班上每名学生的学习能力和知识掌握情况,为同班同学一键布置不同的教学任务。

用心做产品,以诚待客

image.png
洋葱学院自成立之处,便决心搭建于云上。在他们看来,自己研究开源方案或重新搭建系统,是"事倍功半"的,意味着巨大的运维负担;因此,每当有新的业务需求时,洋葱学院都会首先考虑能否使用云上已有方案,他们相信阿里云服务的稳定性、专业性。

洋葱学院将更多的人力和精力投入到了课程研发之中,打磨课程。如今,洋葱的用户遍布全国,深受师生和家长的喜爱,多达3600万学生自发推荐,社科院白皮书显示其教师推荐度和家长满意度分别高达85%和90%。

洋葱学院以匠人之心打造“ 云 ”课堂,是一群践行教育初心的梦想家和冒险家。

]]>
双11大战必备神器——DataV数据可视化-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 突然的降温和强冷空气告诉我,是时候为保暖秋裤来一波购物血拼了。今年的双十一马上到达战场,你准备贡献多少战绩呢!
是不是每年都很期待阿里巴巴汇报双十一最后的成交额,我们可以搬个小板凳一起观看一下那块弥漫着八亿人硝烟的大屏幕。

每年双十一都会出现的这块大屏到底有什么神通广大呢?要想今年双十一吹不一样的牛逼,就要了解一下不一样的神器——DataV数据可视化

专业解释:DataV是一个拖拽式可视化工具,可以在零售、物流、电力、水利、环保,还有交通领域,通过交互式实时数据可视化视屏墙来帮助业务人员发现、诊断业务问题。

它到底有什么样的魅力?

专业级的数据可视化

专精于地理信息与业务数据融合的可视化,提供丰富的行业模版和交互组件,支持自定义组件接入。
组件.jpg

来瞅一眼这个组件有多丰富,DataV的开发小伙伴也太为大家着想了一点。(开发小伙伴还偷偷告诉我,第三方的组件包购买之后也可以接入DataV哦!我真的不是商业间谍)

指挥中心、实时监控、地理分析、汇报展示等等这么多的屏幕模板,真的不用怕没有设计师!
模板.jpg

多种数据源支持

支持接入包括阿里云分析型数据库、关系型数据库、本地CSV上传和在线API等,而且支持动态请求。
数据源.jpg

大数据计算的能力有没有发挥的很棒!(请跟我一起竖起大拇指)

图形化编辑界面

拖拽即可完成样式和数据配置,无需编程就能轻松搭建数据大屏。
拖拽.jpg

只要轻轻动动你的小手指,拖拖拽拽,不需要编程能力,就可以创造出专业的可视化应用!(我真的不是DataV的脑残粉)

灵活部署和发布

适配非常规拼接大屏,创建的可视化应用能够发布分享,没有购买DataV产品的用户也可以访问到应用,作为对外数据业务展示的窗口。或者通过密码/Token的方式进行访问权限控制,保护数据隐私安全。还可以通过历史快照来保存历史版本,并在历史版本之间切换并发布。
发布.jpg

在某些场景,如数据涉密无法上云、展示现场网络条件有限等条件下,还可以采用 DataV 本地部署的方式。

部署和发布如此流畅,不如一起来拖拖拽拽一个可视化界面,展示一下你真正的实力。

双十一期间购买DataV可享受超级福利折扣价!
详情优惠请戳:购买链接

]]>
阿里云物联网平台如何订阅异步服务调用的返回结果?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 业务场景

image.png

1、云端向设备发送的下行消息或者异步服务调用到平台也算结束,平台再向设备进行一个透传
2、设备端向平台发送的上行消息到平台就算结束
3、云端通过服务端订阅来获取设备上行的消息

原理
1、要想获取异步服务调用的返回结果,首先设备得有返回
(1) 设备接收平台端透传过来的异步服务调用
image.png

(2)设备端收到数据后进行响应
image.png
2、这里的设备响应结果发送给平台之后,平台可通过
云产品流传或服务端订阅,再将消息发送给云端
方式一:云平台流转,注意选择Topic:
/${productKey}/${deviceName}/thing/downlink/reply/message
image.png
这个为什么可行?可参考官方文档说明
image.png
然后添加操作为发布到AMQP消费组
image.png

方式二:AMQP服务端订阅
订阅什么呢?勾选设备上报消息即可(前提是设备端有返回,即满足原理1的前提)
image.png

操作步骤
不多说了,直接上步骤
1、准备测试用的产品和设备
主要是定义一个异步服务:这里不详细阐述
image.png

2、准备测试用的云端调试工具
可以是集成云端SDK的Demo,可以是业务逻辑应用调用云端API,最简单的直接使用云端API在线调试工具
具体参数填写规范,这里也不做详细阐述
image.png

3、物联网平台控制台上配置好规则引擎
(1)云平台流转
image.png
选择好产品设备和topic

image.png
注意SQL语句的编写,这里的字段就是要发送给AMQP客户端的消息内容,可以事先进行调试。
image.png
image.png

这里要注意AMQP客户端都是按照既定的协议格式进行过滤数据的,所以这里的消息内容需要按照协议进行配置
image.png

确定好消息内容后

SQL语句:

SELECT timestamp('yyyy-MM-dd'T'HH:mm:ss'Z'') as 云平台流转至AMQP测试,deviceName() as deviceName, code as code,data as data,topic() as topic,messageId() as requestId,id as id,topic(1) as productKey,iotId as iotId FROM "/a16hDZJpRCl/IoTDeviceDemo1thing/downlink/reply/message" WHERE

(2)服务端订阅
image.png
勾选设备上报消息即可,具体消费组怎么创建就不详细阐述

实测效果:
image.png

4、设备端接收消息+响应reply
image.png
image.png

代码示例:
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>MQTTClient</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.eclipse.paho</groupId>
            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

</project>

AliyunIoTSignUtil:

package com.alibaba.taro;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import java.util.Map;

/**
 * AliyunIoTSignUtil
 */

public class AliyunIoTSignUtil {
    public static String sign(Map<String, String> params, String deviceSecret, String signMethod) {
        //将参数Key按字典顺序排序
        String[] sortedKeys = params.keySet().toArray(new String[] {});
        Arrays.sort(sortedKeys);

        //生成规范化请求字符串
        StringBuilder canonicalizedQueryString = new StringBuilder();
        for (String key : sortedKeys) {
            if ("sign".equalsIgnoreCase(key)) {
                continue;
            }
            canonicalizedQueryString.append(key).append(params.get(key));
        }

        try {
            String key = deviceSecret;
            return encryptHMAC(signMethod,canonicalizedQueryString.toString(), key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * HMACSHA1加密
     *
     */
    public static String encryptHMAC(String signMethod,String content, String key) throws Exception {
        SecretKey secretKey = new SecretKeySpec(key.getBytes("utf-8"), signMethod);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);
        byte[] data = mac.doFinal(content.getBytes("utf-8"));
        return bytesToHexString(data);
    }

    public static final String bytesToHexString(byte[] bArray) {

        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
}

Demo:


package com.alibaba;

import com.alibaba.taro.AliyunIoTSignUtil;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;


public class CustomTopicMessageDemo2 {

    public static String productKey = "a16hD*****";
    public static String deviceName = "IoTDevice****";
    public static String deviceSecret = "0895205d*********";
    public static String regionId = "cn-shanghai";


    // 物模型-属性上报topic
    //private static String pubTopic = "/sys/" + productKey + "/" + deviceName + "/thing/event/property/post";
    //private static String subTopic = "/sys/" + productKey + "/" + deviceName + "/thing/service/property/set";
    // 自定义topic,在产品Topic列表位置定义
    //private static String pubTopic = "/"+productKey + "/" + deviceName+"/user/DemoTest";
    //private static String subTopic = "/"+productKey + "/" + deviceName+"/user/DemoTest";
    private static String pubTopic = "/"+productKey + "/" + deviceName+"/user/get";
    private static String subTopic = "/"+productKey + "/" + deviceName+"/user/get";

    private static MqttClient mqttClient;

    public static void main(String [] args){

        initAliyunIoTClient();
//        ScheduledExecutorService scheduledThreadPool = new ScheduledThreadPoolExecutor(1,
//                new ThreadFactoryBuilder().setNameFormat("thread-runner-%d").build());
//
//        scheduledThreadPool.scheduleAtFixedRate(()->postDeviceProperties(), 10,10, TimeUnit.SECONDS);
        // 汇报属性
        //String payloadJson = "{"params":{"MasterLightSwitch":0,"LivingLightSwitch":0,"SecondaryLightSwotch":0,"MasterCurtainSwitch":1,"SecondaryCurtainSwitch":1,"LivingCurtainSwitch":1}}";
        //String payloadJson = "{"params":{"Temp":77,"yyy":{"tttt":"123"}}}";
        String payloadJson = "{"params":{"Temp":77,"yyy":"8888"}}";
        //String payloadJson = "{"tts":"ss"}";
        //String payloadJson = "34454545";
        postDeviceProperties(payloadJson);

        try {
            mqttClient.subscribe(subTopic); // 订阅Topic
        } catch (MqttException e) {
            System.out.println("error:" + e.getMessage());
            e.printStackTrace();
        }

        // 设置订阅监听
        mqttClient.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable throwable) {
                System.out.println("connection Lost");

            }

            @Override
            public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
                String payload =  new String(mqttMessage.getPayload());
                System.out.println(" 接收消息:");
                System.out.println("Topic : " + s);
                System.out.println(payload); //打印输出消息payLoad
                System.out.println("=================================================================");

//                String subTopic = "/sys/" + productKey + "/" + deviceName + "/thing/service/property/set";
//                if(s.equals(subTopic)) {
//                    JSONObject jsonProperty = new JSONObject(payload);
//                    if(jsonProperty.has("params"))
//                    {
//                        String paramsJson = jsonProperty.get("params").toString();
//                        System.out.println("test paramsJson is:n" + paramsJson);
//                        String params = "{"params": " +  paramsJson + "}";
//                        System.out.println("test params is:n" + params);
//                        System.out.println("收到属性设置后,再上报一次属性:");
//                        postDeviceProperties(params);
//                    }
//                }

                //收到服务调用,给予返回reply
//                下行(Alink JSON):
//                请求Topic:/sys/{productKey}/{deviceName}/thing/service/{tsl.service.identifier}
//                响应Topic:/sys/{productKey}/{deviceName}/thing/service/{tsl.service.identifier}_reply
                String subTopic = "/sys/" + productKey + "/" + deviceName + "/thing/service/StartP2PStreaming";
                String replyTopic = "/sys/" + productKey + "/" + deviceName + "/thing/service/StartP2PStreaming_reply";
                if(s.equals(subTopic)) {
                    JSONObject jsonProperty = new JSONObject(payload);
                    if(jsonProperty.has("id"))
                    {
                        String id = jsonProperty.get("id").toString();
                        String replyJson = "{"data":{},"code":200,"id":""+ id +""}";
                        //System.out.println("test replyJson is:n" + replyJson);
                        //String replys = "{"params": " +  replyJson + "}";
                        //System.out.println("test reply is:n" + replys);
                        System.out.println("收到服务调用后,给予返回");
                        postServiceReply(replyJson,replyTopic);
                    }
                }
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {

            }
        });

    }

    /**
     * 初始化 Client 对象
     */
    private static void initAliyunIoTClient() {

        try {
            // 构造连接需要的参数
            String clientId = "java" + System.currentTimeMillis();
            Map<String, String> params = new HashMap<String, String>(16);
            params.put("productKey", productKey);
            params.put("deviceName", deviceName);
            params.put("clientId", clientId);
            String timestamp = String.valueOf(System.currentTimeMillis());
            params.put("timestamp", timestamp);
            // cn-shanghai
            String targetServer = "tcp://" + productKey + ".iot-as-mqtt."+regionId+".aliyuncs.com:1883";

            String mqttclientId = clientId + "|securemode=3,signmethod=hmacsha1,timestamp=" + timestamp + "|";
            String mqttUsername = deviceName + "&" + productKey;
            String mqttPassword = AliyunIoTSignUtil.sign(params, deviceSecret, "hmacsha1");

            connectMqtt(targetServer, mqttclientId, mqttUsername, mqttPassword);

        } catch (Exception e) {
            System.out.println("initAliyunIoTClient error " + e.getMessage());
        }
    }

    public static void connectMqtt(String url, String clientId, String mqttUsername, String mqttPassword) throws Exception {

        MemoryPersistence persistence = new MemoryPersistence();
        mqttClient = new MqttClient(url, clientId, persistence);
        MqttConnectOptions connOpts = new MqttConnectOptions();
        // MQTT 3.1.1
        connOpts.setMqttVersion(4);
        connOpts.setAutomaticReconnect(false);
        connOpts.setCleanSession(false);
        //connOpts.setCleanSession(true);

        connOpts.setUserName(mqttUsername);
        connOpts.setPassword(mqttPassword.toCharArray());
        connOpts.setKeepAliveInterval(60);

        mqttClient.connect(connOpts);
    }

    /**
     * 汇报属性
     */
    private static void postDeviceProperties(String payloadJson) {

        try {
            //上报数据
            //高级版 物模型-属性上报payload
            System.out.println("上报属性值:");
            //String payloadJson = "{"params":{"Status":0,"Data":"15"}}";
            //String payloadJson = "{"GeoLocation":{"Longitude":120.99,"Latitude":30.13,"Altitude":39.01},"BatteryPercentage":40.703533, "Temperature":2.233362}";
            //String payloadJson = "{"id":"3","version":"1.0","params":{"GeoLocation":{"Longitude":120.999,"Latitude":30.13,"Altitude":39.01},"BatteryPercentage":42.99999, "Temperature":2.233362}}";
            //String payloadJson = "{"params":{"MasterLightSwitch":0,"LivingLightSwitch":0,"SecondaryLightSwotch":0,"MasterCurtainSwitch":1,"SecondaryCurtainSwitch":1,"LivingCurtainSwitch":1}}";
            System.out.println(payloadJson);
            MqttMessage message = new MqttMessage(payloadJson.getBytes("utf-8"));
            message.setQos(0);
            mqttClient.publish(pubTopic, message);
            System.out.println("=================================================================");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * 服务返回
     */
    private static void postServiceReply(String payloadJson,String relpyTopic) {

        try {
            //上报数据
            //高级版 物模型-属性上报payload
            System.out.println("服务调用返回:");
            //String payloadJson = "{"params":{"Status":0,"Data":"15"}}";
            System.out.println("Topic:");
            System.out.println(relpyTopic);
            System.out.println(payloadJson);
            MqttMessage message = new MqttMessage(payloadJson.getBytes("utf-8"));
            message.setQos(0);
            mqttClient.publish(relpyTopic, message);
            System.out.println("=================================================================");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}


实测效果:
image.png

5、云端使用AMQP客户端登录,并接收消息

参考官方文档,这里就不作详细阐述。
https://help.aliyun.com/document_detail/143601.html?spm=a2c4g.11186623.6.624.304e354e2OEGFh

代码示例:
pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- amqp 1.0 qpid client -->
<!--        <dependency>-->
<!--            <groupId>org.apache.qpid</groupId>-->
<!--            <artifactId>qpid-jms-client</artifactId>-->
<!--            <version>0.47.0</version>-->
<!--        </dependency>-->


        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.apache.qpid/qpid-jms-client -->
        <dependency>
            <groupId>org.apache.qpid</groupId>
            <artifactId>qpid-jms-client</artifactId>
            <version>0.47.0</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.surefire</groupId>
            <artifactId>maven-surefire-common</artifactId>
            <version>2.12.4</version>
        </dependency>


        <!-- util for base64-->

<!--        <dependency>-->
<!--            <groupId>commons-codec</groupId>-->
<!--            <artifactId>commons-codec</artifactId>-->
<!--            <version>1.3</version>-->
<!--        </dependency>-->
    </dependencies>


</project>

Demo

package com.alibaba;

import org.apache.commons.codec.binary.Base64;
import org.apache.qpid.jms.JmsConnection;
import org.apache.qpid.jms.JmsConnectionListener;
import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.net.URI;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class AmqpJavaClientDemo {

    private final static Logger logger = LoggerFactory.getLogger(AmqpJavaClientDemo.class);

    //业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
    private final static ExecutorService executorService = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors() * 2,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(50000));


    public static void main(String[] args) throws Exception {
        //参数说明,请参见AMQP客户端接入说明文档。
        String accessKey = "LTAI4G2*****";
        String accessSecret = "Mp2f4qopmULI6*****";
        String consumerGroupId = "e0oRIYMSOYwQ*****";
        //iotInstanceId:购买的实例请填写实例ID,公共实例请填空字符串""。
        String iotInstanceId = "";
        long timeStamp = System.currentTimeMillis();
        //签名方法:支持hmacmd5、hmacsha1和hmacsha256。
        String signMethod = "hmacsha1";
        //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
        //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
        String clientId = "yangboClientId";

        //userName组装方法,请参见AMQP客户端接入说明文档。
        String userName = clientId + "|authMode=aksign"
                + ",signMethod=" + signMethod
                + ",timestamp=" + timeStamp
                + ",authId=" + accessKey
                + ",iotInstanceId=" + iotInstanceId
                + ",consumerGroupId=" + consumerGroupId
                + "|";
        //计算签名,password组装方法,请参见AMQP客户端接入说明文档。
        String signContent = "authId=" + accessKey + "&timestamp=" + timeStamp;
        String password = doSign(signContent,accessSecret, signMethod);
        //接入域名,请参见AMQP客户端接入说明文档。
            String connectionUrl = "failover:(amqps://1875496626634053.iot-amqp.cn-shanghai.aliyuncs.com:5671?amqp.idleTimeout=80000)"
                + "?failover.reconnectDelay=30";

        Hashtable<String, String> hashtable = new Hashtable<String, String>();
        hashtable.put("connectionfactory.SBCF",connectionUrl);
        hashtable.put("queue.QUEUE", "default");
        hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
        Context context = new InitialContext(hashtable);
        ConnectionFactory cf = (ConnectionFactory)context.lookup("SBCF");
        Destination queue = (Destination)context.lookup("QUEUE");
        // Create Connection
        Connection connection = cf.createConnection(userName, password);
        ((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);

        System.out.println("connection success");
        // Create Session
        // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
        // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        connection.start();
        // Create Receiver Link
        MessageConsumer consumer = session.createConsumer(queue);
        consumer.setMessageListener(messageListener);
    }

    private static MessageListener messageListener = new MessageListener() {
        @Override
        public void onMessage(Message message) {
            try {
                //1.收到消息之后一定要ACK。
                // 推荐做法:创建Session选择Session.AUTO_ACKNOWLEDGE,这里会自动ACK。
                // 其他做法:创建Session选择Session.CLIENT_ACKNOWLEDGE,这里一定要调message.acknowledge()来ACK。
                // message.acknowledge();
                //2.建议异步处理收到的消息,确保onMessage函数里没有耗时逻辑。
                // 如果业务处理耗时过程过长阻塞住线程,可能会影响SDK收到消息后的正常回调。
                executorService.submit(new Runnable() {
                    public void run() {
                        processMessage(message);
                    }
                });
            } catch (Exception e) {
                logger.error("submit task occurs exception ", e);
            }
        }
    };

    /**
     * 在这里处理您收到消息后的具体业务逻辑。
     */
    private static void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String content = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");


            System.out.println("receive message"
                    + ", topic = " + topic
                    + ", messageId = " + messageId
                    + ", content = " + content);
            logger.info("receive message"
                    + ", topic = " + topic
                    + ", messageId = " + messageId
                    + ", content = " + content);
        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }

    private static JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            logger.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }

        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            logger.error("onConnectionFailure, {}", error.getMessage());
        }

        /**
         * 连接中断。
         */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            logger.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }

        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            logger.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }

        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {}

        @Override
        public void onSessionClosed(Session session, Throwable cause) {}

        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {}

        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {}
    };

    /**
     * 计算签名,password组装方法,请参见AMQP客户端接入说明文档。
     */
    private static String doSign(String toSignString, String secret, String signMethod) throws Exception {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), signMethod);
        Mac mac = Mac.getInstance(signMethod);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(toSignString.getBytes());
        return Base64.encodeBase64String(rawHmac);

//        return Arrays.toString(Base64.encodeBase64(rawHmac));
    }
}

实测效果:
云平台流转方式的返回结果:
image.png

AMQP订阅方式返回结果:
image.png

]]>
视图在SQL中的作用是什么,它是怎样工作的?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

首发公众号:码农架构
视图就是虚拟表:

image.png

如何创建,更新和删除视图

创建视图:CREATE VIEW

CREATE VIEW player_above_avg_height AS
SELECT player_id, height
FROM player
WHERE height > (SELECT AVG(height) from player)

当视图创建之后,它就相当于一个虚拟表,可以直接使用:

SELECT * FROM player_above_avg_height

嵌套视图

CREATE VIEW player_above_above_avg_height AS
SELECT player_id, height
FROM player
WHERE height > (SELECT AVG(height) from player_above_avg_height)

修改视图:ALTER VIEW

ALTER VIEW view_name AS
SELECT column1, column2
FROM table
WHERE condition

删除视图:DROP VIEW

DROP VIEW view_name

需要说明的是,SQLite 不支持视图的修改,仅支持只读视图,也就是说你只能使用 CREATE VIEW 和 DROP VIEW,如果想要修改视图,就需要先 DROP 然后再 CREATE。

如何使用视图简化 SQL 操作

利用视图完成复杂的连接

CREATE VIEW player_height_grades AS
SELECT p.player_name, p.height, h.height_level
FROM player as p JOIN height_grades as h
ON height BETWEEN h.height_lowest AND h.height_highest

利用视图对数据进行格式化

CREATE VIEW player_team AS 
SELECT CONCAT(player_name, '(' , team.team_name , ')') AS player_team FROM player JOIN team WHERE player.team_id = team.team_id

使用视图与计算字段

CREATE VIEW game_player_score AS
SELECT game_id, player_id, (shoot_hits-shoot_3_hits)*2 AS shoot_2_points, shoot_3_hits*3 AS shoot_3_points, shoot_p_hits AS shoot_p_points, score  FROM player_score

总结

使用视图有很多好处,比如安全、简单清晰。
image.png

码农架构-公众号2.jpg

]]>
什么是存储过程,在实际项目中用得多么?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 存储过程是程序化的 SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。存储过程可以说是由 SQL 语句和流控制语句构成的语句集合,它和我们之前学到的函数一样,可以接收输入参数,也可以返回输出参数给调用者,返回计算结果。

存储过程像是函数.

什么是存储过程,如何创建一个存储过程

定义一个存储过程:

CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
    需要执行的语句
END    

删除已经创建的存储过程:

DROP PROCEDURE

更新存储过程:

ALTER PROCEDURE

实现一个简单的存储过程:

CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum; -- 在调用的时候会打印这个值
END

mysql> call study_stored_procedure(50);
+------+
| sum  |
+------+
| 1275 |
+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

使用这个存储过程:

CALL add_num(50);

DELIMITER

如果你使用 Navicat 这个工具来管理 MySQL 执行存储过程,那么直接执行上面这段代码就可以了。如果用的是 MySQL,你还需要用 DELIMITER 来临时定义新的结束符。因为默认情况下 SQL 采用(;)作为结束符,这样当存储过程中的每一句 SQL 结束之后,采用(;)作为结束符,就相当于告诉 SQL 可以执行这一句了。但是存储过程是一个整体,我们不希望 SQL 逐条执行,而是采用存储过程整段执行的方式,因此我们就需要临时定义新的 DELIMITER,新的结束符可以用(//)或者($$)。如果你用的是 MySQL,那么上面这段代码,应该写成下面这样:

DELIMITER //
CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum;
END //
DELIMITER ;

存储过程的 3 种参数类型

image.png
IN 参数必须在调用存储过程时指定,而在存储过程中修改该参数的值不能被返回。而 OUT 参数和 INOUT 参数可以在存储过程中被改变,并可返回。

CREATE PROCEDURE `get_hero_scores`(
       OUT max_max_hp FLOAT,
       OUT min_max_mp FLOAT,
       OUT avg_max_attack FLOAT,  
       s VARCHAR(255)
       )
BEGIN
       SELECT MAX(hp_max), MIN(mp_max), AVG(attack_max) FROM heros WHERE role_main = s INTO max_max_hp, min_max_mp, avg_max_attack;
END

调用:

  • 调用的时候需要在变量前面加 @ , 否则报错
CALL get_hero_scores(@max_max_hp, @min_max_mp, @avg_max_attack, '战士');
SELECT @max_max_hp, @min_max_mp, @avg_max_attack;

流控制语句

  1. BEGIN…END:BEGIN…END 中间包含了多个语句,每个语句都以(;)号为结束符。
  2. DECLARE:DECLARE 用来声明变量,使用的位置在于 BEGIN…END 语句中间,而且需要在其他语句使用之前进行变量的声明。
  3. SET:赋值语句,用于对变量进行赋值。
  4. SELECT…INTO:把从数据表中查询的结果存放到变量中,也就是为变量赋值。
  5. IF…THEN…ENDIF:条件判断语句,我们还可以在 IF…THEN…ENDIF 中使用 ELSE 和 ELSEIF 来进行条件判断。
  6. CASE:CASE 语句用于多条件的分支判断,使用的语法是下面这样的。
  7. LOOP、LEAVE 和 ITERATE:LOOP 是循环语句,使用 LEAVE 可以跳出循环,使用 ITERATE 则可以进入下一次循环。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 BREAK,把 ITERATE 理解为 CONTINUE。
  8. REPEAT…UNTIL…END REPEAT:这是一个循环语句,首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
  9. WHILE…DO…END WHILE:这也是循环语句,和 REPEAT 循环不同的是,这个语句需要先进行条件判断,如果满足条件就进行循环,如果不满足条件就退出循环。
CASE 
  WHEN expression1 THEN ...
  WHEN expression2 THEN ...
  ...
    ELSE 
    --ELSE语句可以加,也可以不加。加的话代表的所有条件都不满足时采用的方式。
END

关于存储过程使用的争议

存储过程有很多好处:
• 存储过程可以一次编译多次使用
• 存储过程的安全性强
• 可以减少网络传输量
缺点也是很明显的:
• 可移植性差
• 调试困难
• 版本管理也很困难
• 不适合高并发的场景

码农架构-公众号2.jpg

]]>
AnalyticDB向量检索+AI 实战: 声纹识别-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、背景

近年来,随着人工智能对传统行业的赋能改造,越来越多的基于人工智能的业务解决方案被提出来,声纹识别在保险行业中的身份认证便是一个很好的例子. 声纹识别是根据说话人发音的生理和行为特征,自动识别说话人身份的一种生物识别技术,对应在电话销售场景下,它主要解决以下安全问题:一方面,有不法分子窃取电话销售人员账号信息,非法获取客户个人信息资料并进行贩卖、泄露,严重侵犯了公民个人的信息隐私权,另一方面,部分行业从业人员利用一些规则漏洞,通过套保、骗保等非法手段实施金融诈骗. 针对这些安全问题,可以通过实时声纹认证加以解决,以电话销售人员为监管核心,利用每个人独一无二的声纹进行严密的个人身份认证,保证电话销售人员对接客户时是本人注册登录,规范电销人员行为,从源头上有效规避信息泄露、漏洞利用等风险。

二、声纹识别原理

640.png

上图是端对端的深度学习训练和推理过程。对比传统声纹识别模型,我们的模型在实际使用中优势明显,在用户远程身份验证场景,通过注册用户说一段话,即可轻松快速的确认注册用户身份,识别准确率达到95%以上,秒级响应,实时声纹核身。下面简要介绍我们模型的特点。

2.0 度量学习

实验发现,在声纹识别中采用softmax进行网络训练 ,用余弦相似度的测试性能往往不如传统声纹识别模型,尤其是在鲁棒性上。分析发现[6]基于softmax的分类训练,为了得到更小的loss,优化器会增大一些easy samples的L2 length,减小hard examples 的L2 length,导致这些样本并没有充分学习,特征呈现放射状,以MNIST识别任务为例,基于softmax学到的特征分布如图3(a)所示. 同类别特征分布并不聚拢,在L2 长度上拉长,呈放射状,且每个类别的间距并不大,在verification的任务中,会导致相邻的两个类别得分很高。

为了达到类内聚拢,类间分散的效果,我们研究了在图像领域中应用较为成功的几种softmax变种,包括AM-softmax[4],arcsoftmax[5]等,从图3(b)可以看到,基于margin的softmax,相比纯softmax,类间的分散程度更大,且类内特征更聚拢,对声纹1:1比对和1:N搜索的任务友好。
2.png

2.1 噪音鲁棒性

在特征提取时,对于简单加性噪音,我们提出了基于功率谱减法,实现噪音抑制;对于其他复杂噪音,我们提出了基于降噪自动编码器的噪音补偿模型,将带噪语音特征映射到干净语音特征,实现噪音消除。

在模型训练时,我们采用数据增强的训练机制,将噪音数据通过随机高斯的形式加入到声纹模型的训练中,使得训练后的模型对噪音数据具有更好的鲁棒性。

2.2 短音频鲁棒性

为了提高短音频鲁棒性,我们提出了基于短时帧级别的模型训练机制,使模型能够在极短的语音时长(约0.5秒)下即可完成声纹识别. 在此基础上,我们在模型训练中引入了更多高阶的音频统计信息和正则化方法,进一步提升了模型在短语音条件下(2~3秒)的识别精度。

三、如何使用AnalyticDB搭建声纹对比系统

3.0 创建插件

使用一下SQL来分别创建AnalyticDB的非结构化分析插件OpenAnalytic和向量检索插件fastann。
20201019144723.jpg

3.1 建表

我们可以建立一个表来保存所有说话人的声音和声音的特征,后续我们可以从这个表中搜索说话人。
3.jpg

3.2 创建索引

我们可以为特征向量列创建向量检索索引。
3.2.jpg

3.3 创建声纹识别算法pipeline

通过以下sql,我们可以在数据库中创建声纹特征提取的算法模型。
3.3.jpg

3.4 获取说话人声纹特征

通过以下sql可以使用3.3创建的pipeline。这个UDF的输入是pipeline名称和目标文本。输出是一个说话人声音的特征向量。
`# 通过声音文件识别
SELECT open_analytic.pipeline_run_dist_random('speaker_feature_extractor',

                    <声音文件>);`

3.5 说话人声纹特征导入AnalyticDB

获取声音特征后, 我们可以使用一下sql来讲数据插入3.1创建的表中。
5.jpg

3.6 在数据库中搜索最相似的的人

通过以下sql,我们可以在声音特征库中搜索最相似的说话人。然后我们可以根据特征间距离是否满足预设的阈值来判断是否是同一个人。
6.jpg

3.7 比较两个声音是否为同一个人

我们还可以提取出两个人的声音特征然后直接计算二者的距离来判断这两个声音是否来自同一个说话人。SQL如下
7.jpg

四、AnalyticDB介绍

分析型数据库(AnalyticDB)是阿里云上的一种高并发低延时的PB级实时数据仓库,可以毫秒级针对万亿级数据进行即时的多维分析透视和业务探索。AnalyticDB for MySQL 全面兼容MySQL协议以及SQL:2003 语法标准, AnalyticDB forPostgreSQL 支持标准 SQL:2003,高度兼容 Oracle 语法生态。

向量检索和非结构化数据分析是AnalyticDB的进阶功能。目前两款产品都包含向量检索功能, 可以支持人脸, 人体, 车辆等的相似查询和推荐系统。AnalyticDB在真实应用场景中可以支持10亿级别的向量数据的查询, 毫秒级别的响应时间。AnalyticDB已经在多个城市的重大项目中大规模部署。

在一般的包含向量检索的的应用系统中, 通常开发者会使用向量检索引擎(例如Faiss)来存储向量数据, 然后使用关系型数据库存储结构化数据。在查询时也需要交替查询两个系统, 这种方案会有额外的开发工作并且性能也不是最优。AnalyticDB支持结构化数据和非结构化数据(向量)的检索,仅仅使用SQL接口就可以快速的搭建起以图搜图或者图片+结构化数据混合检索等功能。AnalyticDB的优化器在混合检索场景中会根据数据的分布和查询的条件选择最优的执行计划,在保证召回的同时,得到最优的性能。AnalyticDB向量版采用了多项创新性技术, 这些技术在我们的论文 AnalyticDB-V: A Hybrid Analytical Engine Towards Query Fusion for Structured and Unstructured Data 中有详细介绍介绍。目前论文已经被数据库三大顶会之一的VLDB接受, 具有技术领先性。

结构化信息+非结构化信息(图片)混合检索在实际应用中被广泛使用的。例如人脸门禁系统被部署在多个小区时, 我们使用一张表存储了所有小区的人脸特征, 在人脸检索时我们只需要检索当前小区的人脸特征。在这种情况下, 使用AnalyticDB我们只需要在SQL中增加where 小区名 ='xxx' 就可以轻易实现。AnalyticDB同时提供了先进的图像文本分析算法, 能够提取非结构化数据的特征和标签, 用户仅仅需要使用SQL就可以完成图像文本内容的分析。

五、参考文献

[1] Heigold G, Moreno I, Bengio S, et al. End-to-end text-dependent speaker verification[C]//2016 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2016: 5115-5119.
[2]Li C, Ma X, Jiang B, et al. Deep speaker: an end-to-end neural speaker embedding system[J]. arXiv preprint arXiv:1705.02304, 2017.
[3] Snyder D, Garcia-Romero D, Sell G, et al. X-vectors: Robust den embeddings for speaker recognition[C]//2018 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2018: 5329-5333.
[4] Wang F, Cheng J, Liu W, et al. Additive margin softmax for face verification[J]. IEEE Signal Processing Letters, 2018, 25(7): 926-930.
[5] Dang J, Guo J, Xue N, et al. Arc face: Additive angular margin loss for deep face recognition[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2019: 4690-4699.
[6] Ranjan R, Castillo C D, Chellappa R. L2-constrained softmax loss for discriminative face verification[J]. arXiv preprint arXiv:1703.09507, 2017.

六、结语

本文介绍了如何使用AnalyticDB来搭建声纹比对系统。AnalyticDB还支持其他多种多样人工智能算法如目标检测, 商品识别, 基因识别等等。想了解更多请用钉钉扫码加入AnalyticDB向量版交流群。

]]>
CNCC 数据库校企合作论坛丨如何构建数据库产业人才培养的生态圈?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1978年,中国人民大学经济信息管理系首任系主任萨师煊第一次将“数据库”这三个字写在人大教室的黑板上,从那一刻起“数据库”技术真正开始在中国的土壤上扎根发芽。

到如今2020年,中国数据库的发展已经历时四十多年,国产数据库在这片土壤上开始逐渐繁茂,从最初的盲目模仿到如今越来越多的数据库企业走向自主研发,国产数据库正在以一种前所未有的速度和力量成长。

计算技术领域的年度盛会 CNCC 将于 10 月 22-24 日在北京盛大召开,届时10月23日下午,“数据库领域校企合作探索”论坛将在北京新世纪日航饭店(主会场)举办,本次论坛邀请了四位行业资深技术专家及学界泰斗带来精彩演讲,从国产数据库在产业实践的经验体会分享,到全新的产教研融合创新模式的探讨,再到数据库行业人才生态战略的深入探索。

本论坛将为大家邀请业界及学界共同探讨如何通过产研合作,充分发挥企业业务场景和高校研究能力的优势,突破核心技术、人才培养等难题,实现中国数据库产业自主创新。

论坛议程

16:00-16:40 杨传辉 蚂蚁集团
蚂蚁集团数据库产业实践

16:40-17:10 钱卫宁 华东师范大学
应用驱动与产教研联动的数据库研发和人才培养实践

17:10-17:40 彭智勇 武汉大学
国产数据库研制人才培养实践

17:40-18:00 梁刘红 蚂蚁集团
构建 OceanBase 全连接的学术合作与人才培养的生态圈

议题简介

嘉宾一:杨传辉(花名:日照)

FD579B4E-1742-4548-9B5D-051DBF678B87.png

嘉宾介绍:蚂蚁集团资深总监、OceanBase 研发总经理,全面负责 OceanBase 研发工作。作为 OceanBase 创始成员和首席架构师,主导了 OceanBase 技术架构设计,实现分布式数据库在核心金融场景零的突破,完成蚂蚁集团核心数据库100%上 OceanBase ,主导 OceanBase TPC-C 测试并打破世界纪录。曾在百度负责云存储与云计算系统研究工作,著有专著《大规模分布式存储系统:原理与实践》。

演讲议题:蚂蚁集团数据库产业实践

议题介绍:蚂蚁集团为何选择自研数据库,自研数据库从0到1的发展历程、技术方案以及后续规划,并分享国产数据库从学术研究到产业实践的经验体会。

嘉宾二:钱卫宁

232.jpg

嘉宾介绍:华东师范大学教授、博士生导师,数据科学与工程学院院长。在复旦大学获得计算机科学与技术学士、硕士与博士学位。上海市优秀学术带头人,入选教育部新世纪人才计划和上海市青年科技启明星计划。作为项目负责人主持包括国家“核高基”重大专项课题、国家重点研发计划课题、国家自然科学基金项目重点项目和面上项目在内的多项科研项目。

目前担任中国计算机学会数据库专委会常务委员,教育部人工智能科技创新专家工作组成员。曾获国家科技进步二等奖奖1次(第二完成人)、上海市科技进步一等奖1次(第一完成人),以及教育部科技进步一等奖、二等奖、教育部自然科学二等奖各1次。研究兴趣包括可扩展事务处理,大数据管理系统基准评测,海量数据分析处理及其应用,以及计算教育学。

演讲议题:应用驱动与产教研联动的数据库研发和人才培养实践

议题介绍:数据库系统是支撑关键核心业务(mission-critical applications)的基础软件。在诸多领域数字化转型的背景下,数据库系统面临着海量数据和互联网级负载的双重压力。自主研发数据库系统,助力行业转型,实现替代工程,无论是对于这些行业领域,还是对于我国的信息技术产业,都具有极为重要的意义。

数据库系统研发的基础知识面广、研究-工程-应用链条长,涉及大规模分布式系统和新硬件利用与优化等前沿技术,需要创新性研究思路,需要核心技术攻坚,还需要大规模工程实现验证,是典型的工程研究问题。报告将介绍我们在应用驱动和校企联动的分布式数据库系统研究和人才培养方面的探索和初步成果,并讨论新的产教研融合创新模式。

嘉宾三:彭智勇

233.jpg


嘉宾介绍:武汉大学教授、博士生导师、大数据研究院副院长,国务院软件工程学科评议组成员,中国计算机学会会士、数据库专业委员会副主任、大数据专家委员会成员。1985年获武汉大学理学学士,1988年获国防科技大学工学硕士,1995年获日本京都大学工学博士。1995-1997年在日本京都高度技术研究所工作,研究员。1997-2000年在美国惠普公司的研究所工作,研究员。提出了一个新的数据库模型:对象代理模型,发表在数据库国际顶级会议 IEEE ICDE 和权威期刊 IEEE TKDE上,得到了学术界认可;分析了开源数据库 PostgreSQL 源代码,出版了《PostgreSQL 数据库内核分析》专著,受到了产业界欢迎;研制了对象代理数据库管理系统 TOTEM,形成了自主知识产权,获教育部科技进步二等奖;目前主要从事对象代理数据库、大数据管理系统、制造业大数据、科技大数据、教育大数据、可信云数据和地理数据水印等方面的研究。

演讲议题:国产数据库研制人才培养实践

议题介绍:研制国产数据库需要大量掌握数据库内核实现技术人才。目前国内各大高校数据库教学过于偏重原理性知识传授以及特定数据库系统的应用技能培养,而忽视了对于数据库管理系统内核实现机制的教学。我们构建了“基础知识-应用技术-科研创新”的分层次数据库教学体系,引进美国斯坦福“数据库系统实现”课程,自编“数据库内核分析”课程教材,引导学生分析数据库管理系统 PostgreSQL 源代码,指导学生参与国产数据库核心技术开发,为华为、阿里、腾讯、百度等企业培养了很多国产数据库研制人才。

嘉宾四:梁刘红

234.jpg

嘉宾介绍:现任蚂蚁集团 OceanBase 合作伙伴和生态合作部总经理。复旦大学计算机系硕士研究生毕业,加入 OceanBase 前,先后供职于微软、IBM,本土云计算创业公司青云 QingCloud,拥有15+年云计算领域及企业软件行业的丰富经验,擅长战略规划,生态合作、商业运营等。

演讲议题:构建 OceanBase 全连接的学术合作与人才培养的生态圈

议题介绍:蚂蚁集团 OceanBase 期望构建全联接的学术和人才生态,助力学术界产研实践和人才培养,与各大高校共同培养兼具理论与实践能力的创新型复合人才。本次报告将发布全连接的学术和人才生态战略。

其中,OceanBase 的研发总经理杨传辉及生态合作部总经理梁刘红也将分别从数据库技术产业发展、数据库产业人才培养等维度为大家带来 OceanBase 的经验分享。

据悉,今年9月,OceanBase 已经与华东师范大学签署战略合作,共建“华东师范大学-北京奥星贝斯分布式数据库联合实验室”,未来 OceanBase 还将连接更多的高校联合培养优秀的科技人才。

OceanBase 坚定地相信分布式数据库就是未来的主流。OceanBase 愿意也期待积极地通过自身实践来加速新的数据库生态的建立,以帮助更多的开发者 / DBA 更好地成长,为社会输出更多的分布式数据库人才。

]]>
分库分表背后那些事儿-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800
  • 概述

    目前行业内OLTP类的数据库单库单表支撑不了几十亿数据的在线处理,包括一些NoSQL数据库,因此对于数据量较大的场景需要进行数据库拆分,如mysql单表建议数据量再500w以内,一些强大的NoSQL单表可以支撑几亿的数据量,但是对于几十亿的数据量一样无法直接支撑,因此我们要做分库分表
    
  • 分库分表类型
    •垂直拆分:充分利用数据库的缓存,提升访问性能。
  •     垂直切分是将一个表的不同属性切分到不同的表中,可以将访问频率高的、长度短的、或者经常一起访问的放在一个表里,
    

    其它的放在另一个表里,从而提升数据库本身缓存的命中率来提升性能,但是单表大数据量依然存在性能瓶颈问题。
    •水平拆分:借用分布式的优势,使用多个数据库的能力来提升存储容量和性能。

    将数据均匀的分布在多个数据库多个表中

    1. 拆分键的选择

    拆分键是水平分库分表的关键,怎么选择拆分键是能否做好分库分表的关键

    1)找到业务主体,确保核心的数据库操作都是围绕这个主体数据进行,然后使用该主体对应的字段作为拆分键

    1)选择拆分键的核心是要保证数据量均匀和请求量均匀

    2)要考虑热点查询语句,尽量保证其不会进行跨库查询

    3)要兼顾关联表,尽可能保证关联表的分库分表规则和主表一致

    1. 非拆分键的加速查询

    1)多维度表法:如订单表,本身按照订单进行拆分,按照实际场景又分别按照买家和卖家生成订单表

    2)缓存映射法:缓存中记录查询条件->拆分键的映射关系,如fpdm+fphm -> fpid

    3)基因融入法: 将查询条件融入到拆分键生成中,如假设分8个库,采用id%8路由,潜台词是,id的后3个bit决定这条数据落在哪个库上,这3个bit就是所谓的基因。

    name -> 基因生成函数 -> name_gene -> 3bit

    id(64 bit) = 生成全局唯一id(61 bit)+ 3bit

    4)数据冗余法:使用外置索引(搜索引擎)或者大数据处理(如hive、hbase)来冗余数据进行解决

    1. 分库分表的查询过程

    分库分表的查询一般使用DRDS、MyCat等中间件来实现,但是用哪款中间件不重要,重要的是我们要了解其核心原理,原理是基础,其他都是表现形式,有了内功之后做什么都无往不利,如令狐冲独孤九剑+吸星大法+易筋经

    1)分片规则:自定义分片策略,主要是根据拆分键值计算出将该条数据放在哪个库哪个表里

    2)JDBC规范重写:针对DataSource、Connection、Statement、PreparedStatement和ResultSet接口封装,对外提供的是逻辑实例,在内部封装多个真实物理实例实现类集合

    3)sql解析:解决sql语法,可以直接使用druid的SQLParser

    4)sql改写:修改逻辑表名->真实表名;替换不支持的功能,如:avg->sum和count;有可能一条sql语句变成多条,如avg查询会变成 2 * 分表数 的sql语句

    5)sql路由:单表路由(不一定落在单表上,如in、between查询)、binding表路由(路由规则一致,如fpzxx和fpmx)、笛卡尔积路由(两个关联表路由不一致,性能很低,占用连接数很高,一般不使用)

    6)sql执行:多线程并发执行sql

    7)结果归并:遍历类、排序类(归并排序)、聚合类(比较型、累加型、平均型)、分组类

    ]]>
    基于DTS+Tablestore的海量订单系统架构设计-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 订单系统概述

    订单场景是人们高频接触的一类场景,无论是线下商场购物、吃喝玩乐消费,还是线上淘宝、会员充值、外卖预定。只要涉及人就会有交易,只要有交易就会产生订单。毫不夸张的讲,所有的应用都会涉及到支付与订单的管理,因此,完善的订单管理架构是每一个架构师或开发人员都要直面的挑战。

    对于订单系统,保证事务与强一致是前提,架构师们通常都会选择MySQL等TP型数据库。但是遇到大规模数据场景下也不得不面临一定的问题。首先,需要通过分库分表等方式提供一个分布式能力提升数据库数据存储、吞吐量,其次数据量大对于查询与聚合等需求难以支持,严重会影响表服务能力与性能。但是在大规模数据下,订单的多维检索、订单分析、以及周期性报表等需求,也是不能够舍弃的。因此需要一个历史订单库,将订单数据派生到其他存储引擎,从而拓展数据的查询、分析、聚合等能力。

    这时,用户会考虑数据双写、数据同步等方式将数据派生一份到搜索引擎或其他NoSQL分布式数据存储,来扩展MySQL性能成本非常高的使用场景。多写的方案一般很少,这对写入一致性项目运维成本与写入性能会有很大的挑战。当前,更被大众所接受的是基于TP数据库的binlog订阅做数据同步,虽然数据同步上会有延时(通常秒级别甚至更低),但是在运维成本、能力扩展、数据一致性上表现优异。

    DTS+Tablestore方案

    本文主要介绍一套基于DTS与Tablestore实现一套完善的订单系统架构。实时订单数据主要针对用户侧的实时生产与修改,实例订单数据则是基于数据同步服务DTS,全、增量订阅TP库中的订单数据,从而保证Tablestore中数据与TP库数据的最终一致性。异步同步的方式不可避免的存在延时,但历史订单库在实时性上要求会适当放宽,但其派生出来的数据在服务能力与功能扩展上得到了极大的提升,尤其是Tablestore这种分布式服务能力强、下游计算生态丰富的NoSQL存储服务。

    系统架构设计

    系统架构如下图,架构基于订单数据的使用功能与实时性分两部分:实时在线订单数据与历史订单数据。

    • • 在线订单数据:存储在TP型数据库,如MySQL、PolarDB等,用于保证订单的强事务能力;
    • • 历史订单数据:存储在Tablestore,分布式存储,支持多维检索与聚合能力,拥有完善的大数据生态;
    • • 同步链路:DTS(数据传输服务),支持全量数据迁移与实时数据同步,实时同步延时秒级别;
      image.png

    Tablestore的能力与生态

    Tablestore的服务性能、分析查询能力以及下游生态,值得着重强调,丰富的能力扩展正是数据派生的核心价值。这里主要展示多元索引、大数据生态两个亮点,更多Tablestore场景与功能,请参考《表格存储Tablestore权威指南》

    • • 多元索引:
      基于倒排索引和列式存储,可以解决大数据的复杂查询难题,包括非主键列查询、全文检索、前缀查询、模糊查询、多字段自由组合查询、嵌套查询、地理位置查询等功能。适用于元数据管理、历史订单维护、地理围栏等场景。

    深入了解多元索引:多元索引官网文档、《TableStore发布多元索引功能,打造统一的在线数据平台》

    • • 大数据架构
      Tablestore 作为一款高性能低成本的存储引擎,海量的数据存储伴随的就是大数据生态对接,并已经形成了一套稳定、高性能的大数据架构,产品在核心功能的升级迭代的过程中,也不断的加强计算引擎对接,目前已经对接了阿里云几个核心计算引擎,包含:MaxCompute、EMR Spark、Blink、DLA 、FC,更总结出一套流批一体处理框架(Lambda plus)。

    深入了解数据中台、大数据体系:《数据中台之结构化大数据存储设计》、《基于 Tablestore 的大数据分析 Lambda 架构 - 云原生、弹性、流批一体》
    架构搭建实战

    准备工作

    1、服务准备

    • • 开通RDS服务:并购买MySQL实例,此处不做详细介绍,可参考《文档》;
    • • 开通Tablestore服务:创建实例(免费),不做详细介绍,可参考《文档》;
    • • 开通DTS服务:并购买MySQL同步Tablestore实例
      目前仅上线MySQL、PolarDB到Tablestore,其中PolarDb需要主动开启binlog开关才能支持增量同步。Tablestore暂时开发上海、北京、深圳,本例使用上海实例。

    image.png

    2、资源准备

    创建子账号AccessKey

    并对子账号实例级别授权,权限为操作Tablestore资源权限,让DTS有权限操作用户该实例资源(注意region跟实例名)

    • • 创建子账号
      image.png
    • • Tablestore实例授权
    {
        "Version": "1",
        "Statement": [
            {
                "Action": "ots:*",
                "Resource": "acs:ots:[myInstanceRegion]:*:instance/[myInstanceName]/*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "ots:ListInstance",
                    "ots:GetInstance"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }

    建表语句

    /******************************************/
    /*   DatabaseName = dts_demo   */
    /*   TableName = order_contract   */
    /******************************************/
    CREATE TABLE `order_contract` (
      `oId` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单id',
      `createTime` datetime NOT NULL COMMENT '下单时间',
      `payTime` datetime DEFAULT NULL COMMENT '支付时间',
      `hasPaid` tinyint(1) NOT NULL COMMENT '是否支付',
      `cId` varchar(20) NOT NULL COMMENT '消费者id',
      `cName` varchar(20) NOT NULL COMMENT '消费者姓名',
      `pBrand` tinytext NOT NULL COMMENT '产品品牌',
      `pCount` mediumint(10) NOT NULL COMMENT '产品数量',
      `pId` varchar(20) NOT NULL COMMENT '产品id',
      `pName` varchar(20) NOT NULL COMMENT '产品名',
      `pPrice` decimal(10,2) NOT NULL COMMENT '产品价格',
      `sId` varchar(20) NOT NULL COMMENT '售货员id',
      `sName` varchar(20) NOT NULL COMMENT '售货员姓名',
      `totalPrice` decimal(10,2) NOT NULL COMMENT '总价格',
      PRIMARY KEY (`oId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='在线订单表';

    用户下单

    直接通过sql写入,模拟用户下单与应用订单数据写入

    INSERT INTO order_contract (oId, payTime, createTime, hasPaid, cId, cName, pBrand, pCount, pId, pName, pPrice, sId, sName, totalPrice)
    VALUES 
    ("00000001", null, "2020-08-05 11:11:11", false, "c00001", "消费者1", "iphone", 1, "p00001", "iphone 7 plus",  9999.80, "s00001", "售货员1", 9999.80),
    ("00000002", null, "2020-08-05 12:11:11", false, "c00001", "消费者1", "iphone", 1, "p00002", "iphone 8 plus",  10999.80, "s00001", "售货员1", 10999.80),
    ("00000003", null, "2020-08-05 13:11:11", false, "c00002", "消费者2", "小米", 2, "p00010", "小米 7 plus",  999.81, "s00001", "售货员1", 1999.62);

    同步配置

    • • 配置DTS实例
      image.png
    • • 配置目标、源配置
      image.png
    • • 配置同步表、字段与类型转换
      image.png

    字段类型映射不建议全部选用默认,根据需求做定制,其中Boolean在MySQL中表现为tinyint(1),需要主动设置成Boolean,时间默认使用String如需转换为时间戳,目标类型主动配置成Integer类型。
    image.png

    启动任务

    启动并进入预检

    image.png
    image.png

    预检完成会自动进入结构迁移(初始化建表)、全量迁移、增量同步阶段。然后用户可以基于DTS控制台查看结构迁移与同步状态。

    目标检查

    结构迁移

    结构迁移完成,Tablestore实例下表初始化成功,主键符合预期
    image.png

    存量数据

    进入存量阶段,开始同步MySQL库已有数据,同步成功后目标库数据可见。
    image.png

    增量校验

    使用样例中的SQL模拟下单、更新订单、删除订单等操作,观察Tablestore实例中表的数据变化

    下单

    INSERT INTO order_contract (oId, payTime, createTime, hasPaid, cId, cName, pBrand, pCount, pId, pName, pPrice, sId, sName, totalPrice)
    VALUES ("00000004", null, "2020-08-05 11:11:11", false, "c00003", "消费者3", "iphone", 1, "p00001", "iphone 7 plus",  9999.80, "s00001", "售货员1", 9999.80);

    支付修改订单状态

    update order_contract set payTime = "2020-08-05 21:11:11", hasPaid = true WHERE oId = "00000004";

    删除数据

    DELETE FROM order_contract WHERE oId = "00000004";

    扩展能力

    多元索引聚合查询

    多元索引对于历史订单的管理,我们曾给出过最佳实践,用户可以参考《基于Tablestore打造亿量级订单管理解决方案》与控制台样例了解功能使用的详细方案。您只需在Tablestore相应的表中直接创建多元索引,便可完成历史订单数据的多条件组合查询、模糊查询、地理位置查询、存在性查询、简单的统计聚合与分析等。通过对产品名做分词,支持模糊查询能力,实例中查询"iphone"关键字,会从全量订单数据中检索出相应的2行数据。
    image.png

    多元索引的订单实战样例,可以参考文章《基于Tablestore打造亿量级订单管理解决方案》,并提供了直观感受的demo样例,如下图。用户可以参考借鉴。

    image.png

    除了多维组合查询,多元索引还是统计、聚合的能力,该能力没有在控制台上暴露,需要通过sdk使用,这里暂不距离,具体使用,可以参考《文档:多元索引的统计聚合》。
    使用统计聚合功能可以实现求最小值、求最大值、求和、求平均值、统计行数、去重统计行数、按字段值分组、按范围分组、按地理位置分组、按过滤条件分组、嵌套查询等;同时多个统计聚合功能可以组合使用,满足复杂的查询需求。

    流批一体的电商大屏

    订单对于电商场景的最根本数据源,如何让海量的订单数据易分析、易可视化是场景的重要需求点,电商大屏或周期交易报表是最直接的数据价值挖掘的方式。这里以大屏为例,大屏可以包含全量订单、实时订单的聚合,全量订单的聚合提供的是全景的综合数据视图,而实时订单的聚合展示的是实时的运营指标数据。
    谈完流批计算对数据价值挖掘的作用,就要见一下实现。Tablestore已经拥有较多的实战案例与架构文章,这里不做重复输出,用户可以直接前往文章《Tablestore结合Spark的流批一体SQL实战》,了解构建方案与效果。大屏效果如下图。
    image.png

    免费专家服务

    欢迎加入Tablestore社区了解产品或参与讨论,更多文章欢迎前往《表格存储Tablestore权威指南》。
    表格存储 (Tablestore) 提供专业的免费的技术咨询服务,期待您的加入。群号 : 23307953
    image.png

    ]]>
    数据中台交付专家告诉你,数据架构的分层怎样更加合理?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800
    -更多关于数智化转型、数据中台内容请加入阿里云数据中台交流群—数智俱乐部 和关注官方微信公总号(文末扫描二维码或点此加入

    -阿里云数据中台官网 https://dp.alibaba.com/index

    作者:柯根

    从整体上看,数据中台体系架构可分为:数据采集层、数据计算层、数据服务层三大层次。通过这三大层次对上层数据应用提供数据支撑。

    数据采集层

    对于企业来说,每时每刻都在产生海量的数据,数据采集作为数据体系第一环尤为重要。

    因此在数据采集层需要建立了一套标准的数据采集体系方案,并致力全面、高性能、规范地完成海量数据的采集,将其传输到大数据平台。

    互联网日志采集体系包括两大体系:Web端日志采集技术方案;APP端日志采集技术方案。

    在采集技术之上,企业可以用面向各个场景的埋点规范,来满足日志数据打通等多种业务场景。同时,还可以建立了一套高性能、高可靠性的数据传输体系完成数据从生产业务端到大数据系统的传输;在传输方面,采集技术可既包括数据库的增量数据传输,也包括日志数据的传输;既需要能支持实时流式计算、也能实时各种时间窗口的批量计算。另一方面,也通过数据同步工具直连异构数据库(备库)来抽取各种时间窗口的数据。

    下图展示数据采集层在数据分层中的位置:
    1.png

    数据计算层

    从采集系统中收集了大量的原始数据后,数据只有被整合、计算才能被用于洞察商业规律、挖掘潜在信息,实现大数据价值,达到赋能商业、创造商业的目的。从采集系统中收集到的大量原始数据,将进入数据计算层中被进一步整合与计算。

    面对海量的数据和复杂的计算,数据计算层包括两大体系:数据存储及计算云平台和数据整合及管理体系。

    - 数据存储及计算云平台
    例如,MaxCompute是阿里巴巴自主研发的离线大数据平台,其丰富的功能和强大的存储及计算能力使得企业的大数据有了强大的存储和计算引擎;StreamCompute是阿里巴巴自主研发的流式大数据平台,在内部较好地支持了企业流式计算需求。

    - 数据整合及管理体系
    “OneModel”是数据整合及管理的方法体系和工具,大数据工程师在这一体系下,构建统一、规范、可共享的全域数据体系,避免数据的冗余和重复建设,规避数据烟囱和不一致,充分发挥在大数据海量、多样性方面的独特优势。借助这一统一化数据整合及管理的方法体系,构建企业数据公共层,并可以帮助相似大数据项目快速落地实现。

    数据中台数据加工链路也是遵循业界的分层理念:包括操作数据层(ODS,Operational Data Store)、明细数据层(DWD,Data Warehouse Detail)、汇总数据层(DWS, Data Warehouse Summary)和应用数据层(ADS,Application Data Store)。通过数据中台不同层次之间的加工过程实现从数据资产向信息资产的转化,并且对整个过程进行有效的元数据管理及数据质量处理。

    下图展示数据公共层(ODS+DWD+DWS)与数据应用层(ADS)在数据分层中的位置:
    2.png
    图:数据公共层与数据应用层关系

    (1)统一数据基础层
    我们通过各种方式采集到的丰富数据,在清洗、结构化后进入统一的ODS数据基础层。

    其主要功能包括:
    -同步:结构化数据增量或全量同步到数据中台
    -结构化:非结构化(日志)结构化处理并存储到数据中台
    累积历史、清洗:根据数据业务需求及稽核和审计要求保存历史数据、数据清洗

    在权责方面,所有数据应该在源头统一,统一所有的数据基础层,并由一个团队负责和管控,其他团队无权复制数据基础层的数据。

    (2)数据中间层
    我们进行数据建模研发,并处理不因业务特别是组织架构变动而轻易转移的数据中间层。包括DWD明细数据中间层和DWS汇总数据中间层。

    其主要功能包括:
    -组合相关和相似数据: 采用明细宽表,复用关联计算,减少数据扫描。
    -公共指标统一加工:基于OneData体系构建命名规范、口径一致和算法统一的统计指标,为上层数据产-品、应用和服务提供公共指标;建立逻辑汇总宽表;
    -建立一致性维度:建立一致数据分析维度表,降低数据计算口径、算法不统一的风险。

    在权责方面,面向业务提供服务之前,由统一的团队负责从业务中抽象出源于业务而又不同于业务的数据域,再主导统一建设数据中间层,包括侧重明细数据预JOIN等处理的明细中间层、侧重面向应用可复用维度和指标的汇总数据中间层。特别是要由唯一团队负责将核心业务数据统一加入数据中间层。允许部分业务数据有独立的数据团队按照统一的OneModel体系方法论建设数据体系,ODS数据基础层和DWD+DWS数据中间层因其统一性和可复用性,被称为数据公共层。

    (3)数据应用层
    在面向应用提供服务时,业务团队或深入业务线的数据团队有极大的自由度,只要依赖数据公共层,即可自由的建设ADS数据应用层。

    其主要功能包括:
    -个性化指标加工:不公用性;复杂性(指数型、比值型、排名型指标)
    -基于应用的数据组装:大宽表集市、横表转纵表、趋势指标串

    数据服务层

    当数据已被整合和计算好之后,需要提供给产品和应用进行数据消费,为了更好的性能和体验,需要构建数据服务层,通过接口服务化方式对外提供数据服务。针对不同的需求,数据服务层的数据源架构在多种数据库之上,如Mysql和Hbase等。

    数据服务可以使应用对底层数据存储透明,将海量数据方便高效地开放给集团内部各应用使用。如何在性能、稳定性、扩展性等多方面更好地服务用户;如何满足应用各种复杂的数据服务需求;如何保证数据服务接口的高可用。随着业务的发展,需求越来越复杂,因此数据服务也在不断地前进。

    不管是数据公共层还是应用层,最终都需要面向业务提供服务。为了让业务部门找数据、看数据、用数据更加方便,我们将OpenAPI升级为能缓解业务变化对数据模型冲击的包括方法论+产品在内的OneService体系,使其在提供统一的公用服务的同时,兼容面向个性化应用的服务。

    下图为数据服务层在数据分层中的位置:
    3.png
    图:数据应用层与数据服务层关系

    综上,企业数据中台依托数据采集层、数据计算层、数据服务层,为上层数据产品、业务系统等提供数据支撑。云上数据中台产品Dataphin从“采、建、管、用”为企业提供一站式数据中台各层次的实现,配合阿里云系列产品,可实现企业数据中台全链路稳定、高效构建。


    数据中台是企业数智化的必经之路,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。

    目前正通过阿里云对外输出系列解决方案,包括通用数据中台解决方案零售数据中台解决方案金融数据中台解决方案互联网数据中台解决方案政务数据中台解决方案等细分场景。

    其中阿里云数据中台产品矩阵是以Dataphin为基座,以Quick系列为业务场景化切入,包括:

    官方站点:
    数据中台官网 https://dp.alibaba.com
    钉钉沟通群和微信公众号
    数据中台钉钉群二维码2.jpg

    ]]>
    【福利】实时计算及 Flink 社区招聘信息汇总-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 招聘.jpg

    大家好,为帮助大家更好的找到适合的岗位,拿到心仪公司的offer,社区收集了多家实时计算及 Flink 企业用户的招聘需求,如:知乎、VIPKID、新氧、作业帮、得物、好未来、阿里巴巴等招聘信息,整理如下,供大家参考。

    欢迎更多企业投递实时计算及 Flink 相关招聘信息,更欢迎 Flink 专家、技术爱好者自荐。相关需求可联系小松鼠(微信 ID:Ververica2019)。

    本期招聘企业及岗位如下,找工作或有意向换工作的同学快到碗里来!

    image.png

    知乎 | 大数据实时处理平台研发工程师
    VIPKID | 实时计算研发工程师
    得物APP | 实时数据开发
    作业帮 | 实时计算架构研发工程师
    新氧科技 | 大数据开发工程师
    好未来 | 实时计算 Flink 专家岗 / 数据平台开发 - Flink 实时&离线岗
    阿里云计算平台事业部 | 研发工程师

    知乎:大数据实时处理平台研发工程师

    知乎数据架构团队正在围绕 Apache Flink 为核心打造大数据实时计算平台,为知乎内部业务提供批流融合的大数据计算能力,加入知乎数据架构团队共同探索新一代的大数据技术。欢迎大家推荐简历!

    岗位描述

    1. 参与 Apache Flink 的二次开发,建设 Flink 批流融合大数据计算引擎
    2. 深入理解业务并与业务部门密切合作,基于 Apache Flink + Kubernetes 建设知乎的实时计算平台

    岗位要求

    1. 计算机、通信、数学等相关专业,具备良好的计算机技术基础
    2. 熟悉 Java,具备扎实的数据结构和算法基础
    3. 具备良好的沟通和团队协作能力,做事主动积极,有技术热情和激情面对挑战
    4. 深入理解 Flink 或 Spark Streaming 原理者优先
    5. 有 PB 级数据处理经验和实时计算平台开发经验者优先
    6. 熟悉 Hadoop Ecosystem 例如 -- Flink/Spark/Hadoop/Hive/Kafka/Pulsar 以及 Kubernetes,向社区贡献过代码者优先

    简历投递

    • 工作地点:北京
    • 简历投递:sunxiaoguang@zhihu.com

    VIPKID:实时计算研发工程师

    岗位职责

    1.参与实时计算平台建设和架构设计开发,维护与优化;
    2.负责海量数据的采集,清洗等工作。

    岗位要求

    1.5年以上工作经验;
    2.熟练掌握hadoop、spark、storm、flink等大数据相关组件,深入理解系统原理,并有丰富Flink开发及运维经验
    3.精通数据采集、实时计算
    4.3年以上的Java或scala开发经验(熟练使用java、scala、python等开发语言);
    5.了解数据结构及算法(基本算法即可)
    6.具有较强的学习能力,自我管理能力、驱动能力
    
    加分项:
    
    有过主导和设计实时计算平台的系统规划,并落地的经验优先

    简历投递

    • 工作地点:北京
    • 简历投递:yangliang@vipkid.com.cn

    得物APP:实时数据开发

    岗位职责

    负责公司实时相关的业务数据统计需求的开发,包括实时数仓,标签体系,日志和业务数据的etl等工作。

    岗位要求

    1. 熟悉 Java、Scala 中至少一门语言,熟练使用 SQL。
    2.有大数据组件使用经验,熟悉大数据相关技术,如 Spark、Flink、HBase、Hive、Clickhouse 等。
    3. 使用 Flink 开发过复杂业务,熟悉 Datastream 和 Table/Sql API,有flink 作业线上调优经验优先。
    4.良好的业务理解能力和沟通表达能力,主动性强。

    简历投递

    • 工作地点:上海市杨浦区互联宝地
    • 简历投递:luoziyu@theduapp.com

    作业帮:实时计算架构研发工程师

    岗位职责

    1. 负责作业帮业务数据内容规划和设计,实现数据互通共享体系,解决海量数据面临的挑战;
    2. 负责公司流量数据OLAP引擎和实时计算框架的设计、开发和优化,构建实时数据的公共层;
    3. 负责Spark、Flink集群的管理和优化,保证集群持续稳定;
    4. 根据业务需求进行上游数据平台设计开发,打造高可用的数据平台;

    岗位要求

    1. 有一线互联网公司2年数据开发经验,或独立负责大中型业务大数据实时架构的经验;
    2. 熟悉Linux系统,具备Java/Scala/Python等一种或几种语言的开发能力;
    3. 熟悉Flink/Spark/Kafka/Presto/Hadoop/HBase等大数据相关技术,对源码有研究或者有调优经验者优先;
    4. 熟悉并行计算或者分布式计算原理,熟悉高并发、高稳定性、海量数据的系统特点和技术方案;
    5. 有大数据系统平台项目经验,掌握实时数据处理系统搭建和开发;
    6. 学习能力强,热衷开源技术,有团队观念,具备独立解决问题的能力;

    简历投递

    • 工作地点:北京
    • 简历投递邮箱:zhangying14@zuoyebang.com

    北京新氧科技有限公司:大数据开发工程师

    岗位职责

    1. 负责新氧实时业务数据支撑;
    2. 主要参与新氧流量、内容、电商、会员等各主题域实时数据仓库开发。
    3. 负责实时数据仓库程序上线并持续迭代优化以及日常运维

    岗位要求

    1. 本科以上,计算机相关专业;
    2. 3年以上的大数据研发经验,有flink实时作业开发部署调优经验。对kafka有较深入研究,对性能调优、故障恢复有一定的处理经验。
    3. 熟悉Linux操作环境,有良好的至少一门语言 (Java、Scala) 开发调试经验;
    4. 熟悉大数据开发相关技术,如hadoop、hive、spark、kafka、 spark streaming、Flink等;
    5. 熟练数据仓库,对多维数据建模有深入理解;
    6. 对数据系统热爱,乐于解决具有挑战性的问题, 具备优秀的分析问题、解决问题能力;

    简历投递

    • 工作地点:北京
    • 简历投递:gaohongchao@soyoung.com;liuyuquan@soyoung.com

    好未来:实时计算 Flink 专家岗 / 数据平台开发 - Flink 实时&离线岗

    实时计算 Flink 专家岗

    岗位职责

    1.负责行业实时计算开发平台的架构设计,完善实时计算方案
    2.支撑公司内部的实时业务开发
    3.对 Flink 以及周边技术进行源码级探索改进

    岗位要求

    1.有实时计算引擎设计项目经验,并能完成相应系统设计研发
    2.有丰富的Flink线上部署/日常运维/性能分析/故障定位能力
    3.精通 Java 编程语言,计算机基础知识(网络/操作系统/分布式基础等)扎实
    4.有过 SQL 引擎开发经验,或者对编译原理有所了解
    5. 有过完整实时开发平台项目经验者优先

    简历投递

    • 工作地点:北京
    • 简历投递:liuwenlin@tal.com

    数据平台开发:Flink 实时&离线岗

    岗位职责

    离线
    1.参与集团数据中台大数据基础设施建设
    2. 负责大数据生态组件的调优和二次开发
    3. 负责大数据平台开发套件相关子系统开发
    实时
    1.实现行业实时计算开发平台
    2. 支撑公司内部的实时业务开发
    3. 对flink以及周边技术进行源码级探索改进

    岗位要求

    1.3-5年相关工作经验,计算机相关专业本科以上学历
    2.精通java语言,熟悉常用设计模式、主流开发框架,3年以上相关开发经验;

    离线

    1.参与过大数据开发套件产品的后端开发者优先,包括大数据离线任务调度系统,元数据管理系统,数据质量系统等。
    2. 熟悉Hadoop集群及相关生态组件(HDFS、Zookeeper、YARN、Hive、Spark、Kerberos、AirFlow、Flink、Presto、Kudu等),有调优和二次开发经验者优先。
    3. 熟悉集群权限控制及开发者优先
    4. 熟悉数据安全管理和实践经验优先

    实时

    1. 有实时计算引擎设计项目经验,并能完成相应系统设计研发
    2. 有丰富的Flink线上部署/日常运维/性能分析/故障定位能力
    3. 有过SQL引擎开发经验,或者对编译原理有所了解
    4. 有过完整实时开发平台项目经验者优先

    简历投递

    • 工作地点:北京
    • 简历投递:liuwenlin@tal.com

    阿里云计算平台事业部:研发工程师

    团队介绍

    1. 基于Hadoop、Spark、Hive、HBase、Flink,Kafka,TensorFlow等开源大数据组件,构建云原生大数据平台,提供大规模计算能力;
    2. 提供一站式大数据应用开发平台, 包括大数据安全体系,数据治理,作业调度,交互式查询,专项领域监控,通过机器学习算法帮助用户智能诊断复杂问题;
    3. 打造世界顶级的开源大数据平台,在公共云、裸机、私有云和混合云等环境, 为阿里云客户提供云原生大数据全家桶服务;
    4. 参与阿里云城市大脑、智慧交通等多项国家战略项目建设,利用实时/批技术处理真实世界中的海量数据。

    职位描述

    1. 计算机、通信、数学等相关专业,具备良好的计算机技术基础;
    2. 熟悉Java/C++/Python/GO等至少一种编程语言,具备扎实的数据结构和算法基础;
    3. 具备良好的沟通和团队协作能力,做事主动积极,有技术热情和激情面对挑战;
    4. 熟悉主流JAVA框架,包括spring、netty、mybatis等技术者优先;
    5. 熟悉Hadoop/Spark/Flink/Storm/HBase/Hive/Kafka/TensorFlow/Kubernetes/Prometheus技术者优先。

    简历投递

    • 工作地点:北京
    • 简历投递:wali.rcd@taobao.com

    ▼ 实时计算 Flink 技术交流群 ▼

    实时计算交流群 200.jpeg

    ]]>
    凯伦陈洪进:中国防水行业亟待拥抱数字化变革 | 阿里CIO学院名人堂-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png

    9月10日,陈洪进与来自全国各地的36家大型企业的43位信息化高管共聚美丽的北国春城,参加由阿里巴巴与中国一汽联合举办的阿里CIO学院走进一汽共创会,从智能制造、新营销和新IT三个角度探寻企业转型中的“数字化力量”。

    活动期间,陈洪进与其他学员们就企业数字化转型进行了深入交流。会后,陈洪进接受CIO学院的新媒体访谈,他表示对传统的制造企业而言,数字经济带来的不是数字化转型,而是数字化升级,中国防水行业亟待拥抱数字化变革。

    image.png

    走进一汽感受数字化力量

    在参加完走进一汽的活动后,陈洪进对两个内容感触较深:

    第一,数字化工厂的实践。对传统的制造型企业而言,红旗数字化工厂不像媒体上宣传的各种类型的无人产线那么高不可攀,现场有很多人在不同工序从事各种作业,各种AGV设备也有明显地自行改造的影子,但通过优化现场流程实现了生产效率的大幅度提升。除了遍布全厂的AGV小车外,无人值守库房、工序配件的自动调度、车间电子看板取代纸质单据等内容,都令人印象深刻,值得借鉴。

    第二,产品数字化在价值链上的拓展。一汽提出根据服务的价值链不同,构建不同的数字孪生主体,包括研发孪生、制造孪生、营销孪生、运营孪生等。当前传统制造型企业讨论的孪生大多偏重于设备领域,以设备模拟和预防性维护为主要方向,一汽的孪生理念拓展了产品数字化的边界,以产品为载体,整合企业价值链的各种数据形成适应每个价值模型的数字化产品,这种理念超越了对个别业务进行数字孪生的认知,对于指导制造型企业进行全面数字化转型具有很好的引领作用。

    image.png

    陈洪进在活动现场

    凯伦股份全力加速数字化转型

    凯伦股份是中国建筑防水行业首家创业板上市公司,缔造了传统制造领域民营企业6年上市的传奇速度。2017年上市至今,年复合增长率63%。

    凯伦股份在信息化建设上具有前瞻性。2012年工厂投产即投用了定制的金蝶ERP系统,良好的支撑了企业运营和上市;上市后,2018年即着手实施Oracle ERP系统,于2019年1月1日顺利上线,支持了企业由单一工厂到多工厂、单一组织到多组织的快速发展。

    2019至2020年进一步实施了MES、HR、BI、智能化大屏等系统。

    凯伦的信息化面临的主要挑战是如何为公司的快速发展保驾护航,涉及集团管控、异地工厂管理、新业务模式涌现等课题。凯伦所在的行业是建材领域,面对的客户主要是传统的建筑工程市场客户,产品非常标准化,属于典型的“toB”业务,企业管理的重点是提升自身的运营效率。

    根据如上情况,凯伦提出了“数字化运营,可视化管理”的信息系统三年战略目标,要求在经营、生产的全过程实现数字化,同时对每个职能通过报表、大屏等实现可视化管理。进而凯伦设定了核心的管控指标:部门KPI指标覆盖率,要求由系统出具考核数据的部门KPI指标占比60%以上。

    数字化转型?数字化升级!

    在下午举行的阿里CIO学院走进一汽共创会上,来自中国一汽数字化部、启明信息、阿里巴巴的6位讲师针对 “新制造”、“新基建”、“新营销”等话题进行了深度分享,深度讲解以云数智为代表新兴数字技术正在为汽车产业赋能,加速行业向智能化、电动化、网联化、共享化的出行服务转型。

    陈洪进表示,对传统的制造企业而言,数字经济带来的不是数字化转型,而是数字化升级。以阿里为代表的互联网企业带来了很多新技术、新理念、新做法,值得传统制造企业借鉴。

    2019年,阿里的“中台”概念火热却在制造业鲜有案例,关键原因应该是制造业中很少涉及一个公司内要在较短的时间内、由不同部门实现类似业务快速上线的应用场景。但在不到一年的时间内,苏州的一家企业使用中台、微服务的理念,以单点登录为切入点,重构了企业内部系统,极大提升了员工使用业务系统的效率;在一汽参观中,启明信息更是提出基于中台架构,通过微服务设计,将传统的ERP系统拆解成分布式、开放式和服务化的业务平台。

    在当前各行各业都面临巨大挑战,业务的瞬息万变,要求对应的信息系统具有良好的弹性,开放化、服务化将成为永恒的主题。在这种背景下,中台、微服务的理念对于在现有的业务系统基础上,快速构建全新的业务支持系统具有重要的理念引导作用,无疑是阿里输出给社会的成功经验。架构中台化也将是凯伦未来三年要重点实现的技术目标。

    在陈洪进看来,阿里还有很多如云平台、弹性计算这些可以直接使用的技术,阿里的管理理念也是当前企业管理界的热点。传统制造业应该充分借鉴和应用这些理念和技术,提升自身的管理能力和经营效率,加快转型和升级,以更好的状态参与市场竞争。

    image.png

    陈洪进参与圆桌论坛环节

    数字化转型人才和开放合作至关重要

    当前中国防水市场正在加速转型,目前规模每年2000亿元,绝大部分业务是toB业务,但头部企业已经涉足C端市场,直接面向消费者;同时,随着凯伦为代表的高分子防水市场的拓展,中国的防水行业深入参与国际市场竞争也是可以预见的未来。

    在这种背景下,陈洪进认为,以下三个方面是防水行业数字化升级的主要内容:首先,以“提质、降本、增效”为核心的内部管理的数字化升级;其次,面向防水工程的施工过程管理的数字化升级;再次,面向家装领域即C端的业务模式、营销模式的数字化创新,未来防水行业的数字化升级的亮点就在这三个方向上。

    更重要的是,传统制造型企业转型,对业务部门而言是理念的挑战,对IT部门而言是技术和业务的双重挑战,这可能是制造型企业的共同痛点。业务部门由于不懂IT技术,很难理解IT技术的边界,也很少会用IT的视角诠释业务需求,所以需要在理念上认同数字化;IT部门首先要立足业务需求解决业务问题,其次要用更合理的技术确保可扩展性,两个方面要兼顾。归根结底,企业需要培养数字化复合型人才,这是一个永恒的话题。

    陈洪进指出,凯伦需要培育核心的数字化复合型人才,需要兼顾业务和IT技术。制造型企业的信息化往往不是技术创新驱动型,需要精通行业特点的人才,立足于业务本身,使用IT技术解决实际问题。防水行业如今整体信息化水平一般的问题核心是复合型人才稀缺。凯伦的目标不仅仅局限于国内市场,所以必须培养出核心的数字化复合型人才,才能支持各种可能的业务方向。

    对于技术,陈洪进倾向于开放合作。他认为制造型企业不像互联网行业一样有众多精英IT人才。所以,凯伦应该有懂得IT边界的、精通业务流程的人才,能阐明业务需求,而对于业务的具体技术实现,可以根据实际情况选择。

    在陈洪进看来,大数据和人工智能将会带来行业的巨变。他记得在此次参观中,一汽的领导在分享业务对象数字化的认识时,提出:一个设备,最早只是一个代码,不到1K的信息;当数据累计到10M时,可以支持设备的全生命周期管理;1G时,可以开展预防性维修;10G时,可以支持虚拟仿真。这个例子比较形象的说明了业务对象的数据累积到一定程度,大数据、人工智能等技术可以带来量变到质变的过程。

    “因为相信,所以看见”,“仰望星空,脚踏实地”,陈洪进表示,阿里的这两句话最代表他对大数据、人工智能技术的感触。

    阿里CIO学院应考虑建立行业分院

    陈洪进:首先很感激阿里给CIO创造的这样一个公益平台,让大家有机会深入学习和了解阿里,也有机会互相学习。通过阿里CIO学院的组织走进中国一汽,观摩标杆企业的精益制造;领略前沿的数字化转型,学习先进的数字化方法论;专家老师全程相伴,并与阿里巴巴和中国一汽的数字化高管面对面交流,倾听实战分享、可谓一场完美的主题活动。

    我觉得未来可以考虑行业分院的形式,让学员更加有针对性。同时也可以开展分院之间的交流互动,取长补短。

    公司简介

    image.png

    凯伦股份是一家集防水材料研发、制造、销售及施工服务于一体的国家高新技术企业。先后获得“中国防水行业质量提升示范企业”、“中国房地产500强首选供应商”、“江苏省优秀企业”等荣誉称号。企业以“融合防水”为旗,于国内首创MBP高分子自粘胶膜防水卷材和白色抗流挂聚氨酯防水涂料,填补了国内高分子防水领域的技术空白,引领行业转型升级。2017年10月26日,凯伦股份实现A股上市,成为中国建筑防水行业首家创业板上市公司。

    公司目前在华东、华北、华中和西南建有现代化生产基地,引进全套进口成型生产线和先进的胶体磨等设备,精细化和自动化程度达到国际同行先进水平。为快速响应市场,公司不断完善产业布局,在西北、华南和华东(新)投资建设产业基地,抓住长三角生态绿色一体化发展示范区机遇,在苏州建设世界一流的高分子防水材料生产基地。

    秉承“精细化的融合、看得见的诚实”的经营理念,公司短短几年内把业务推向多个建筑防水领域,先后与万科、恒大、碧桂园、绿地等20多个龙头房地产建立战略合作关系,承接了多个高铁、地铁、隧道、核电站和地下管廊等防水项目;凯伦股份放眼全球,销售遍布美国、英国、德国、澳大利亚、以色列、新加坡、印度等60多个国家和地区。

    凯伦股份坚守“高品质”和“绿色生产”的战略定位,致力成为国际领先的功能性建材制造商,以稳健的步伐,确保基业长青,效力百年建筑,造福人类,回馈社会。

    文章来源:阿里飞天CIO学堂微信公众号

    名人堂

    名人堂是阿里CIO学院打造的一档大伽访谈栏目,每周一期。以推动企业创新与数智化升级为愿景,通过采访行业顶尖客户,帮你更好地了解和思考企业数字化转型中可能面临的挑战,梳理行业痛点和方法路径,从而相互滋养,共同成长。

    更多文章

    点击查看青岛华通集团智能研究院明磊:拥抱数字化转型正在成为CIO头等大事 | 阿里CIO学院名人堂>>>

    点击查看ERIC,ZHANG:地产业数字化转型按下“加速键”美好人居正当时 | 阿里CIO学院名人堂>>>

    点击查看李锋:联接数智化生态,打造“数字化越秀” | 阿里CIO学院名人堂>>>

    点击查看龙湘君:基金行业奏响数字化转型五部曲 | 阿里CIO学院名人堂>>>

    点击查看段晓力:从一个小目标的对立 到万个小目标的融合 | 阿里CIO学院名人堂>>>

    点击查看贾坤:扶贫基金事业借数字化转型助力决胜脱贫攻坚战 | 阿里CIO学院名人堂>>>

    点击查看郑荣:世界的香格里拉——从“藏在深闺无人识”到“网红旅游目的地” | 阿里CIO学院名人堂>>>

    点击查看魏琴:前海再保险以科技创新打破保险行业传统疆域 | 阿里CIO学院名人堂>>>

    点击查看王丽静:深挖“数据宝藏”打造新型高端智库 | 阿里CIO学院名人堂>>>

    点击查看石廷洪:推动数字化转型 打造世界一流企业 | 阿里CIO学院名人堂>>>

    点击查看韩海潮:数字化浪潮引领基金行业转型新方向 | 阿里CIO学院名人堂>>>

    点击查看谢钧棋:以“数”转型 用“脉”管理 | 阿里CIO学院名人堂>>>

    ]]>
    应用架构之道:分离业务逻辑和技术细节-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 头图.png

    作者 | 张建飞  阿里巴巴高级技术专家

    架构

    什么是架构?

    关于架构这个概念很难给出一个明确的定义,也没有一个标准的定义。

    硬是要给一个概述,我认为架构就是对系统中的实体以及实体之间的关系所进行的抽象描述。

    架构始于建筑,是因为人类发展(原始人自给自足住在树上,也就不需要架构),分工协作的需要,将目标系统按某个原则进行切分,切分的原则,是要便于不同的角色进行并行工作。

    为什么需要架构?

    有系统的地方就需要架构,大到航空飞机,小到一个电商系统里面的一个功能组件都需要设计和架构。

    我很喜欢《系统架构:复杂系统的产品设计与开发》里面的一句话:结构良好的创造活动要优于毫无结构的创造活动

    与之相对应的,现在很多敏捷思想提倡 no design,只要 work 就好。期待好的架构可以在迭代中自然涌现。这个想法有点太理想化了,在现实中,只要能 work 的代码,工程师是很少有动力去重构和优化的。

    架构师的职责

    作为架构师,我们最重要的价值应该是“化繁为简”。但凡让事情变得更复杂,让系统变得更晦涩难懂的架构都是值得商榷的。

    架构师的工作就是要努力训练自己的思维,用它去理解复杂的系统,通过合理的分解和抽象,使哪些系统不再那么难懂。我们应该努力构建易懂的架构,使得在系统上工作的其他人员(例如设计者、实现者、操作员等)可以较为容易地理解这个系统。

    软件架构

    软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通信。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。

    软件架构为软件系统提供了一个结构、行为和属性的高级抽象,由构件的描述、构件的相互作用、指导构件集成的模式以及这些模式的约束组成。软件架构不仅显示了软件需求和软件结构之间的对应关系,而且指定了整个软件系统的组织和拓扑结构,提供了一些设计决策的基本原理。

    软件架构的核心价值应该只围绕一个核心命题:控制复杂性。他并不意味着某个特定的分层结构,某个特定的方法论(贫血、DDD 等)。

    软件架构分类

    在介绍应用架构之前,我们先来看一下软件架构的分类。

    随着互联网的发展,现在的系统要支撑数亿人同时在线购物、通信、娱乐的需要,相应的软件体系结构也变得越来越复杂。软件架构的含义也变得更加宽泛,我们不能简单地用一个软件架构来指代所有的软件架构工作。按照我个人理解,我将软件架构划分为:

    1.png

    业务架构:由业务架构师负责,也可以称为业务领域专家、行业专家。业务架构属于顶层设计,其对业务的定义和划分会影响组织结构和技术架构。例如,阿里巴巴在没有中台部门之前,每个业务部门的技术架构都是烟囱式的,淘宝、天猫、飞猪、1688 等各有一套体系结构。而后,成立了共享平台事业部,打通了账号、商品、订单等体系,让商业基础实施的复用成为可能。

    应用架构:由应用架构师负责,他需要根据业务场景的需要,设计应用的层次结构,制定应用规范、定义接口和数据交互协议等。并尽量将应用的复杂度控制在一个可以接受的水平,从而在快速的支撑业务发展的同时,在保证系统的可用性和可维护性的同时,确保应用满足非功能属性要求(性能、安全、稳定性等)。

    分布式系统架构:分布式系统基本是稍具规模业务的必选项。它需要解决服务器负载,分布式服务的注册和发现,消息系统,缓存系统,分布式数据库等问题,同时架构师要在 CAP(Consistency,Availability,Partition tolerance)之间进行权衡。

    数据架构:对于规模大一些的公司,数据治理是一个很重要的课题。如何对数据收集、数据处理提供统一的服务和标准,是数据架构需要关注的问题。其目的就是统一数据定义规范,标准化数据表达,形成有效易维护的数据资产,搭建统一的大数据处理平台,形成数据使用闭环。

    物理架构:物理架构关注软件元件是如何放到硬件上的,包括机房搭建、网络拓扑结构,网络分流器、代理服务器、Web服务器、应用服务器、报表服务器、整合服务器、存储服务器和主机等。

    运维架构:负责运维系统的规划、选型、部署上线,建立规范化的运维体系。

    典型应用架构

    分层架构

    分层是一种常见的根据系统中的角色(职责拆分)和组织代码单元的常规实践。常见的分层结构如下图所示:

    2.png

    CQRS

    CQS(Command Query Separation,命令查询分离),最早来自于 Betrand Meyer(Eiffel 语言之父,OCP 提出者)提出的概念。其基本思想在于,任何一个对象的方法可以分为两大类:

    • 命令(Command): 不返回任何结果(void),但会改变对象的状态。
    • 查询(Query): 返回结果,但是不会改变对象的状态,对系统没有副作用。

    3.png

    六边形架构

    六边形架构是 Alistair Cockburn 在 2005 年提出,解决了传统的分层架构所带来的问题,实际上它也是一种分层架构,只不过不是上下,而是变成了内部和外部(如下图所示)。

    4.png

    六边形架构又称为端口-适配器架构,这个名字更容器理解。六边形架构将系统分为内部(内部六边形)和外部,内部代表了应用的业务逻辑,外部代表应用的驱动逻辑、基础设施或其他应用。

    适配器分为两种类型(如下图所示),左侧代表 UI 的适配器被称为主动适配器(Driving Adapters),因为是它们发起了对应用的一些操作。而右侧表示和后端工具链接的适配器,被称为被动适配器(Driven Adapters),因为它们只会对主适配器的操作作出响应。

    5.png

    洋葱圈架构

    洋葱架构与六边形架构有着相同的思路,它们都通过编写适配器代码将应用核心从对基础设施的关注中解放出来,避免基础设施代码渗透到应用核心之中。这样应用使用的工具和传达机制都可以轻松地替换,可以一定程度地避免技术、工具或者供应商锁定。

    不同的是洋葱架构还告诉我们,企业应用中存在着不止两个层次,它在业务逻辑中加入了一些在领域驱动设计的过程中被识别出来的层次(Application,Domain Service,Domain model,Infrastructure等)。

    另外,它还有着脱离真实基础设施和传达机制应用仍然可以运行的便利,这样可以使用 mock 代替它们方便测试。

    6.png

    在洋葱架构中,明确规定了依赖的方向:

    • 外层依赖内层;
    • 内层对外层无感知。

    COLA 应用架构

    COLA 架构是我团队自主研发的应用架构,目前已经开源。在 COLA 的设计中,我们充分汲取了经典架构的优秀思想。除此之外,我们补充了规范设计和扩展设计,并且使用 Archetype 的方式,将架构固化下来,以便可以快速的在开发中使用。

    COLA 开源地址:https://github.com/alibaba/COLA

    分层设计

    COLA 的分层是一种改良了的三层架构。主要是将传统的业务逻辑层拆分成应用层、领域层和基础实施层。如下图所示,左边是传统的分层架构,右边是 COLA 的分层架构。

    7.png

    其每一层的作用范围和含义如下:

    1)展现层(Presentation Layer):负责以 Rest 的格式接受 Web 请求,然后将请求路由给 Application 层执行,并返回视图模型(View Model),其载体通常是 DTO(Data Transfer Object);

    2)应用层(Application Layer):主要负责获取输入,组装上下文,做输入校验,调用领域层做业务处理,如果需要的话,发送消息通知。当然,层次是开放的,若有需要,应用层也可以直接访问基础实施层;

    3)领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Entities)的函数对外部提供业务逻辑的计算和处理;

    4)基础实施层(Infrastructure Layer)主要包含 Tunnel(数据通道)、Config 和 Common。这里我们使用 Tunnel 这个概念来对所有的数据来源进行抽象,这些数据来源可以是数据库(MySQL,NoSql)、搜索引擎、文件系统、也可以是 SOA 服务等;Config 负责应用的配置;Common 是通用的工具类。

    扩展设计

    对于只有一个业务的简单场景,对扩展性的要求并不突出,这也是为什么扩展设计常被忽略的原因,因为我们大部分的系统都是从单一业务开始的。但是随着业务场景越来越复杂,代码里面开始出现大量的if-else逻辑。此时除了常规的策略模式以外,我们可以考虑在架构层面提供统一的扩展解决方案。

    在扩展设计中,我们提炼出两个重要的概念,一个是业务身份,另一个是扩展点

    业务身份是指业务在系统唯一标识一个业务或者一个场景的标志。在具体实现中,我们使用 BizCode 来表示业务身份,其中 BizCode 采用类似 Java 包名命名空间的方式。例如,我们可以用“ali.tmall”表示阿里天猫业务,用“ali.tmall.car” 表示阿里天猫的汽车业务,而用"ali.tmall.car.aftermarket"代表这是阿里天猫的汽车业务的后市场场景。

    每个业务或者场景都可以实现一个或多个扩展点(ExtensionPoint),也就是说一个业务身份加上一个扩展点,可以唯一地确定一个扩展实现(Extension)。而这个业务身份和扩展点的组合,我们将其称之为扩展坐标(ExtensionCoordinate),如下图所示。

    8.png

    这样,通过业务身份+扩展点,我们就可以从框架层面实现对不同租户,不同业务,不同场景的扩展定制了。整个阿里业务中台正是基于这个思想,实现的多业务支撑的。

    规范设计

    任何事物都是规则性和随机性的组合。规范的意义就在于我们可以将规则性的东西固化下来,尽量减少随心所欲带来的复杂度,一致性可以降低系统复杂度。从命名到架构皆是如此,而架构本身就是一种规范和约束,破坏这个约束,也就破坏了架构。

    COLA 制定了一些列的规范:包括组件(Module)结构、包(Package)结构、命名等。

    比如对于组件,我们要求使用 COLA 的应用都应该遵循如下图所示的组件划分:

    9.png

    COLA 架构总览

    在架构思想上,COLA 主张像六边形架构那样,使用端口-适配器去解耦技术细节;主张像洋葱圈架构那样,以领域为核心,并通过依赖倒置反转领域层的依赖方向。最终形成如下图所示的组件关系。

    10.png

    换一个视角,从 COLA 应用处理响应一个请求的过程来看。COLA 使用了 CQRS 来分离命令和查询的职责,使用扩展点和元数据来提升应用的扩展性。整个处理流程如下图所示:

    11.png

    应用架构的核心

    纵观上面介绍的所有应用架构,我们可以发现一个共同点,就是“核心业务逻辑和技术细节分离”。

    12.png

    是的,六边形架构、洋葱圈架构、以及 COLA 架构的核心职责就是要做核心业务逻辑和技术细节的分离和解耦。

    试想一下,业务逻辑和技术细节糅杂在一起的情况,所有的代码都写在 ServiceImpl 里面,前几行代码是做 validation 的事,接下来几行是做 convert 的事,然后是几行业务处理逻辑的代码,穿插着,我们需要通过 RPC 或者 DAO 获取更多的数据,拿到数据后,又是几行 convert 的代码,在接上一段业务逻辑代码,然后还要落库,发消息.....等等。

    再简单的业务,按照上面这种写代码的方式,都会变得复杂,难维护。

    因此,我认为应用架构的核心使命就是要分离业务逻辑和技术细节。让核心业务逻辑可以反映领域模型和领域应用,可以复用,可以很容易被看懂。让技术细节在辅助实现业务功能的同时,可以被替换。

    最后我们发现,应用架构的道就是:“让上帝的归上帝,凯撒的归凯撒。”

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    玩转ECS第1讲 | 云上自动化部署和运维的正确姿势-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 演讲嘉宾简介:吴君印,阿里云资深技术专家。负责ECS整体服务层面的技术和产品架构工作,并负责阿里云智能内部OnECS的技术和产品架构工作,包括产品ECS云助手,运维编排OOS,资源编排ROS以及内部OnECS产品宙斯,致力于打造以ECS为中心的系统管理、自动化和DevOps体验。

    以下内容根据演讲视频以及PPT整理而成。观看回放
    更多课程请进入玩转ECS详情页”了解

    本次分享主要围绕以下三个方面:

        一、云上部署和运维的特点
        二、资源编排ROS
        三、运维编排OOS
    

    今天主要分享的内容是云上自动化部署和运维的正确姿势,下面先来看看云上部署相比于传统的RDC部署有哪些不同。

    一、云上部署和运维的特点

    无论是部署还是运维,在云上都有如下四个特点:

    image.png

    首先,可重复。在云上部署相对于传统RDC部署而言更加灵活,只需要编写一次模版就可以随时随地拉起一套环境,做到一键部署。目前有两种类型的环境部署,一种是测试环境、预发环境、生产环境。第二种是在不同地域进行部署,如北京地域、上海地域以及杭州地域。

    第二点,多环境保持一致。因为使用的是相同的模版进行部署,所有环境部署出来的结果都一样,这样可以避免人为错误,避免问题排查时的环境影响,环境造成的问题往往是最难排查的问题之一。

    第三点,可审计。所有操作均通过API,所有API操作都可以被审计,集成操作审计服务ActionTrail即可。

    第四点是DevOps。从环境部署到应用部署都模板化,版本管理使用Git,可以做代码评审、代码回滚。

    资源编排ROS和运维编排OOS

    阿里云针对云上部署和运维特点,推出了两个产品,包括资源编排ROS(Resource Orchestration Service)——解决自动化部署问题,运维编排OOS(Operation Orchestration Service)——解决自动化运维问题。两款编排产品除了支持ECS的实例,还支持其它阿里云的产品,如负载均衡,关系型数据库RDS,对象存储OSS等。

    image.png

    二、资源编排ROS

    资源编排ROS的典型场景

    资源编排ROS的典型场景主要有四种:
    • 第一种是部署模版,资源编排ROS是通过模版方式达到可以重复部署的目的,使用模版可以在任何时间任何地点拉起一套环境。
    • 第二种是MSP、ISV提供自己的部署模版,可以一键开出复杂的业务系统,如SAP HANA等系统,将部署时间缩短为几个小时。由于云上的环境都是标准的,只要有测试通过后的模版就可以在不同的环境、不同的账号中重复部署。
    • 第三是解决方案中心,阿里云通过自身多年服务客户和双11的经验,总结了大量的最佳实践,在解决方案中心中提供了很多高质量的模版,支持开箱即用。
    • 第四是CI/CD集成,在DevOps开发模式下,只有将部署模版放到CI/CD中才能打造DevOps的开发模式,轻松的做到蓝绿部署,并且支持阿里云云效。

    image.png

    ROS控制台及操作演示

    下图是资源编排ROS主页https://rosnext.console.aliyun.com/ ,上方对ROS产品进行了简单的介绍;下方是常见的部署架构作为示例模版。

    image.png

    左侧菜单栏中有“我的模版”和“模版示例”,其中我的模版是需要自定义的模版,模版示例中提供了大量常见的部署形式,如Jenkins、Kafka等。解决方案中心中是由阿里云解决方案架构师团队、最佳实践团队、各业务方团队和资源编排团队合作共建,将阿里云多年沉淀的最佳实践和针对各种场景的解决方案沉淀为资编排源模版,用户可以使用这些最佳实践模版使得云上部署更加安全高效。

    image.png

    “资源类型”模块中展示了ROS支持的阿里云云产品。

    image.png

    下面以构建我的模版LNMP-deom-1作为例子,模版以JSON格式表达,也支持YAML格式,最重要的是,还提供了可视化的架构图。

    image.png

    可以发现所有的资源都在VPC内部,包括关系型数据库RDS和ECS实例。图中两个ECS实例挂在负载均衡LoadBalance下面。

    image.png

    接着可以使用此模版创建资源栈,之后通过事件tab知道每一步创建步骤。在资源tab中看到所有被模版创建的资源,只需要点击资源ID,就可以打开实例详情页面。在输出tab在有显示一个网站链接,可以发现此次网站部署成功。参数tab中提供了每次模版的参数。当用户手动修改一些资源,与模版出现不一致时,可以使用检查资源偏差查看不同点。

    image.png

    三、运维编排OOS

    运维编排OOS的典型场景

    运维编排OOS的典型场景同样分为四种:
    • 首先是批量操作实例和执行远程命令,例如启动、停止等,相比于其它方式无需密码,无需登录,无需使用跳板机,且无需担心安全问题,运维编排使用了阿里云RAM进行控制。
    • 第二种场景是定时运维,在固定的时间执行制定的命令,相当于云上Cron服务,并且免托管,分布式。
    • 第三种场景是支持报警和事件驱动运维,当某个事件发生时自动触发告警任务。
    • 第四是提供大量丰富的公共模版,阿里云总结了大量的典型运维场景,并将总结成果开源到了Github上,欢迎大家贡献优质模版,共同打造运维社区。

    image.png

    OOS控制台及操作演示

    下图是运维编排OOS主页https://oos.console.aliyun.com/ ,左侧菜单栏中有批量操作实例模块,任务类型包含发送进程命令、批量下载文件、实例操作、实例属性修改等。批量管理软件模块中可以批量的给实例下载和安装常见的软件,在我的软件模块可以自行部署和卸载。

    image.png

    定时开关机模块中可以选择在指定的时间关闭、开启或重启实例。在包年包月的服务器情况下,客户需要在固定的时间升级临时宽带,等高峰过去后再下降,以达到节约成本的目的。在创建更新镜像模块中可以基于已有的实例进行更新,也可以基于已有的镜像创建实例,进一步更新,再创建新的镜像。

    定时运维模块可以在固定的时间和固定的地域执行指定的任务。告警与事件运维模块中若控制台上显示当某个事件发生时自动触发模版,比如CPU使用率过高时重启实例操作。

    image.png

    所有的模版都提供了可视化视图,提供了更加直观的展示方式,还提供了YAML和JSON两种格式的文本,方便用版本管理软件如Git进行管理。

    image.png

    下图展示的是批量操作实例,发送远程命令,命令内容是发送输出命令。之后选择实例,可以手动选择,可以指定实例标题,也可以指定实例资源组,或者上传csv文件,从ECS实例表中导出csv文件来选择实例。

    image.png

    在高级选项部分可以配置执行模式,如出现错误时继续执行还是暂停实例,设置并发速率,允许的最大错误次数等。

    image.png

    此外还有更加快速的执行实例命令方式,在实例列表模块中选择具体的实例,进入实例详情页后会显示本实例远程命令,显示了历史的执行命令,同时可以发送新的远程命令。其次在实例列表中同时选择多台实例,选择更多,发送远程命令,这时多台实例就可以同时执行命令。

    image.png

    使用ROS、OOS的部分阿里云产品

    下图中列出了支持ROS、OOS的常见阿里云产品,包括ACS容器服务、FC函数计算、SLS日志服务、SMC服务迁移中心等等,这与产品本身的部署场景契合。

    image.png

    云产品需要支持多种地域,阿里云有22个地域,使用ROS和OSS可以最大提高部署和运维效率。阿里云对内部系统变更有非常严格的要求,需要提供信息完整的变更单、申请、审批、以及需要为变更过程中可能出现的问题提前准备脚本。因此OSS会预先提供变更模版和回滚模版,从而提供自动化运维程度,降低人工错误。

    客户对自动化运维有不同的需要,从下图左侧可以分出运维的几个层次,从最底层的手动运维、到半手工,半自动化运维、再到高度自动化运维、标准化运维以及智能运维(AIOps)、大部分客户的需求集中在中间三层,大部分的公司处于半手工,半自动化运维,异或高度自动化的方式,少部分的公司更加激进的走到了更加标准化运维,享受到了更加DevOps的方式,阿里云自动化部署ROS和自动化运维OOS的主打场景可以满足这三个主要层次的自动化需求。

    image.png

    今天的分享到此结束,感兴趣的同学可持续关注云上自动化部署ROS和运维OOS产品动态。

    ]]>
    数据湖解决方案——广告行业解读-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 行业综述

    游戏市场需求旺盛,行业景气度持续提升
    据相关数据显示,我国广告投放刊例花费整体呈负增长趋势,2019年2月下降幅度最大达到18.3%,下降幅度虽然有所放缓,但是整体而言,我国广告投放刊例花费下降幅度仍然较大。2020年1月,我国广告投放刊例花费下滑5.6%。
    分媒体来看,传统广告媒介主要包括报纸媒体、杂志、广播、电视、户外媒体和售点等。2020年1月,报纸、杂志、广播等较为传统的广告媒介刊例下降幅度较大,报纸、杂志等纸媒下降了30%以上,广播媒介下降了19.5%。电视、传统户外同样下降了4.9%和9%;整体而言,传统媒体广告刊例花费呈下降趋势。
    与传统媒体广告相比,互联网广告市场规模逐渐上升。根据中关村互动营销实验室的数据,2010-2019年我国互联网广告市场增速虽然放缓,但是我国互联网广告市场规模呈现出逐年上升的发展趋势,2010-2019年我国互联网广告市场规模复合增速达40%以上。2019年我国互联网广告总收入约4367亿元人民币,相较上年增长18.22%,增幅较2018年略有放缓,但仍保持增长的态势。

    行业发展方向

    互联网广告时代正在来临,大数据驱动智能化精准广告投放
    随着互联网的普及,以及精准化程度高、性价比高、媒体质量优等优势,互联网广告迅速崛起,不断持续冲击传统媒介,市场份额持续上升:2016-2019年,我国互联网广告规模占整体广告市场规模比重持续上升,到2019年,互联网广告所占比重已超过50%。

    互联网媒体广告相对于传统广告而言具有较多优势,例如互联网广告形式更新频率比报刊广告、广播广告、电视广告等传统媒体更快,能快速适应市场环境的变化,是高度综合的媒体,扩容性高。互联网媒体广告的发展迅速较快,已不仅仅是展示广告,而是更加精准化、个性化和自动化,能够紧紧地把握市场潮流以及引领着技术的进步。
    

    面临的痛点

    大数据驱动下数据存储成难题,资源浪费成难题
    互联网广告平台的增加,一方面为读者提供了更多的阅读平台,但是背后产生了大量的阅览、订阅、购买数据,而这些数据的产生已经从过去的TB级向PB级甚至EB级跨越,而要利用好这些数据,需要大量的存储空间和计算资源,尤其是为了提高广告推送的准确率以及点击率,需要对大量的数据进行复杂的运算,而这给企业提出了难题。持有的资源过多,虽然可以满足业务需求,但是在空闲期间限制,如果不持有相应的资源,那么又不能很好的解决业务问题。如何很好的寻找业务和资源消耗的平衡点,摆在了各个广告投放企业眼前。

    数据湖解决方案

    阿里云数据湖解决方案,助力企业真正释放数据价值
    15.png
    基于阿里云对象存储OSS构建的数据湖解决方案,可以全面满足数据的存储、离线分析、交互查询等各种业务诉求,帮助解决上面提到的这些难题。
    首先,数据湖解决方案可为用户存储的数据提供高达12个9的可靠性,让数据安全存放,保障用户数据不丢不坏。

     其次,阿里云的数据湖解决方案,也是一套十分智能的解决方案。其中对象存储OSS,可以对接个多业务系统,存储来自不同业务系统的多种数据源,如些系统的原始数据、游戏日志数据等。等数据汇聚到数据湖之后,它的上层系统可以兼容多种计算引擎,如开源大数据引擎像Hive,Spark,阿里云EMR、DLA等,帮助用户便捷地实现数据处理和分析,不需要再重复拷贝多份。同时采用 Jindofs提供缓存加速方案,还可以获得比使用HDFS更好的体验。

    这样一套整体的数据存储、处理分析解决方案,能很大程度地减少系统兼容性问题,管理维护也更加简单,帮助IT人员从复杂且繁琐的运维中解放出来,更加专注在产品创新和业务模式的运营上。

    最佳案例实践

    客户简介
    客户是一家致力于为广告主企业构建贯穿消费者生命周期的流量网络,形成企业私有化的消费者数字资产。在国内享有很高的知名度。

    业务挑战
    1、智能流量平台的数据量在急剧增长,每天的业务日志数据在持续的产生和上传,曝光、点击、推送的数据在持续处理,每天新增的数据量已经在5TB左右,对整个数据处理系统提出了更高的要求。
    2、通过运用AI图像识别技术对内容场景进行智能识别与自动匹配,实现不同场景下的创意的精准匹配,真正实现千人千面千种场景的沟通。
    3、不断升级AI运算技术能力,建立不同营销目的下的流量协同过滤模型,实时进行流量优选,实现不同场景下的流量的高效使用。另外,从触达、互动到消费、忠诚,全面升级消费者各阶段的沟通体验,增值企业消费者数字资产。

    解决方案
    1、利用DLA+ OSS极致分析能力来应对业务波峰波谷。一方面轻松应对来自品牌客户的临时分析。另一方面利用DLA的强大计算能力,分析按月、季度广告投放,精确计算出一个品牌下面会有多少个活动,每个活动分媒体,分市场,分频道,分DMP的投放效果,进一步增强了加和智能流量平台为品牌营销带来的销售转化率。
    2、DLA提供的Serverless的弹性服务为按需收费,不需要购买固定的资源,完全契合业务潮汐带来的资源波动,满足弹性的分析需求,同时极大地降低了运维成本和使用成本。

    客户价值
    为客户的智能流量平台提供了性价比极高的处理方案。
    1、相对性价比提升30%,无需专门维护人员,按量付费,成本低。
    2、临时业务需求承接率提升200%~300%。
    3、即需即用,准备成本低,响应快速。平均任务耗时降低67%。
    4、代码通用,支持数据业务无缝迁移。
    5、海量资源存储在OSS上,对业务支撑效果超过自建平台。
    6、计费简明,业务成本方便计算。

    ]]>
    MySQL死锁系列-线上死锁问题排查思路-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 前言

    MySQL 死锁异常是我们经常会遇到的线上异常类别,一旦线上业务日间复杂,各种业务操作之间往往会产生锁冲突,有些会导致死锁异常。这种死锁异常一般要在特定时间特定数据和特定业务操作才会复现,并且分析解决时还需要了解 MySQL 锁冲突相关知识,所以一般遇到这些偶尔出现的死锁异常,往往一时没有头绪,不好处理。

    本篇文章会讲解一下如果线上发生了死锁异常,如何去排查和处理。除了系列前文讲解的有关加锁和锁冲突的原理还,还需要对 MySQl 死锁日志和 binlog 日志进行分析。

    线上死锁异常分析

    正文

    日常工作中,应对各类线上异常都要有我们自己的 SOP (标准作业流程) ,这样不仅能够提高自己的处理问题效率,也有助于将好的处理流程推广到团队,提高团队的整体处理异常能力。

    所以,面对线上偶发的 MySQL 死锁问题,我的排查处理过程如下:

    1. 线上错误日志报警发现死锁异常
    2. 查看错误日志的堆栈信息
    3. 查看 MySQL 死锁相关的日志
    4. 根据 binlog 查看死锁相关事务的执行内容
    5. 根据上述信息找出两个相互死锁的事务执行的 SQL 操作,根据本系列介绍的锁相关理论知识,进行分析推断死锁原因
    6. 修改业务代码

    根据1,2步骤可以找到死锁异常时进行回滚事务的具体业务,也就能够找到该事务执行的 SQL 语句。然后我们需要通过 3,4步骤找到死锁异常时另外一个事务,也就是最终获得锁的事务所执行的 SQL 语句,然后再进行锁冲突相关的分析。

    第一二步的线上错误日志和堆栈信息一般比较容易获得,第五步的分析 SQL 锁冲突原因中涉及的锁相关的理论在系列文章中都有介绍,没有了解的同学可以自行去阅读以下。

    下面我们就来重点说一下其中的第三四步骤,也就是如何查看死锁日志和 binlog 日志来找到死锁相关的 SQL 操作。

    死锁日志的获取

    发生死锁异常后,我们可以直接使用 show engine innodb status 命令获取死锁信息,但是该命令只能获取最近一次的死锁信息。所以,我们可以通过开启 InnoDB 的监控机制来获取实时的死锁信息,它会周期性(每隔 15 秒)打印 InnoDb 的运行状态到 mysqld 服务的错误日志文件中。

    InnoDb 的监控较为重要的有标准监控(Standard InnoDB Monitor)和 锁监控(InnoDB Lock Monitor),通过对应的系统参数可以将其开启。

    -- 开启标准监控
    set GLOBAL innodb_status_output=ON;
    -- 关闭标准监控
    set GLOBAL innodb_status_output=OFF;
    -- 开启锁监控
    set GLOBAL innodb_status_output_locks=ON;
    -- 关闭锁监控
    set GLOBAL innodb_status_output_locks=OFF;

    另外,MySQL 提供了一个系统参数 innodb_print_all_deadlocks 专门用于记录死锁日志,当发生死锁时,死锁日志会记录到 MySQL 的错误日志文件中。

    set GLOBAL innodb_print_all_deadlocks=ON;

    死锁日志的分析

    通过上述手段,我们可以拿到死锁日志,下图是我做实验触发死锁异常时获取的日志(省略的部分信息)。

    该日志会列出死锁发生的时间,死锁相关的事务,并显示出两个事务(可惜,多事务发生死锁时,也只显示两个事务)在发生死锁时执行的 SQL 语句、持有或等待的锁信息和最终回滚的事务

    下面,我们来一段一段的解读该日志中给出的信息,我们按照图中标注的顺序来介绍:

    TRANSACTION 2078, ACTIVE 74 sec starting index read // -1 事务一的基础信息,包括事务ID、活跃时间,当前运行状态

    表示的是 ACTIVE 74 sec 表示事务活动时间,starting index read 为事务当前正在运行的状态,可能的事务状态有:fetching rows,updating,deleting,inserting, starting index read 等状态。

    mysql tables in use 1, locked 1  // -2 使用一个table,并且有一个表锁
    LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1  // -3 涉及的锁结构和内存大小 

    tables in use 1 表示有一个表被使用,locked 1 表示有一个表锁。LOCK WAIT 表示事务正在等待锁,3 lock struct(s) 表示该事务的锁链表的长度为 3,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁或 autoinc 锁等。heap size 1136 为事务分配的锁堆内存大小。

    2 row lock(s) 表示当前事务持有的行锁个数,通过遍历上面提到的 11 个锁结构,找出其中类型为 LOCK_REC 的记录数。undo log entries 1 表示当前事务有 1 个 undo log 记录,说明该事务已经更新了 1条记录。

    下面就是死锁日志中最为重要的持有或者待获取锁信息,如图中-5和-6行所示,通过它可以分析锁的具体类型和涉及的表,这些信息能辅助你按照系列文章的锁相关的知识来分析 SQL 的锁冲突

    RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2078 lock_mode X locks rec but not gap  // -5 具体持有锁的信息
    RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2078 lock_mode X locks rec but not gap waiting // -6 等待获取锁的信息

    《锁类型和加锁原理》 一文中我们说过,一共有四种类型的行锁:记录锁,间隙锁,Next-key 锁和插入意向锁。这四种锁对应的死锁日志各不相同,如下:

    • 记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap
    • 间隙锁(LOCK_GAP): lock_mode X locks gap before rec
    • Next-key 锁(LOCK_ORNIDARY): lock_mode X
    • 插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention

    所以,按照死锁日志,我们发现事务一持有了 test.t 表上的记录锁,并且等待另一个记录锁。

    通过死锁日志,我们可以找到最终获得锁事务最后执行的 SQL,但是如果该事务执行了多条 SQL,这些信息就可能不够用的啦,我们需要完整的了解该事务所有执行的 SQL语句。这时,我们就需要从 binlog 日志中获取。

    binlog的获取和分析

    binlog 日志会完整记录事务执行的所有 SQL,借助它,我们就能找到最终获取锁事务所执行的全部 SQL。然后再进行具体的锁冲突分析。

    我们可以使用 MySQL 的命令行工具 Mysqlbinlog 远程获取线上数据库的 binlog 日志。具体命令如下所示:

    Mysqlbinlog -h127.0.0.1 -u root -p --read-from-remote-server binlog.000001 --base64-output=decode-rows -v

    其中 --base64-output=decode-rows 表示 row 模式 binlog日志,所以该方法只适用于 row 模式的 binlog日志,但是目前主流 MySQL 运维也都是把 binlog 日志设置为 row 模式,所以这点限制也就无伤大雅。-v 则表示将行事件重构成被注释掉的伪SQL语句。

    我们可以通过死锁日志中死锁发生的具体事件和最终获取锁事务正在执行的SQL的参数信息找到 binlog 中该事务的对应信息,比如我们可以直接通过死锁日志截图中的具体的时间 10点57分和 Tom1、Teddy2 等 SQL 的具体数据信息在 binlog 找到对应的位置,具体如下图所示。

    根据 binlog 的具体信息,我们可以清晰的找到最终获取锁事务所执行的所有 SQL 语句,也就能找到其对应的业务代码,接下来我们就能进行具体的锁冲突分析。

    小节

    死锁系列终于告一段落,如果大伙有什么疑问或者文中有什么错误,欢迎在下方留言讨论。也希望大家继续持续关注。

    个人博客,欢迎来玩

    ]]>
    【升级】10月消息队列MQ升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列MQ】【升级通知】

    升级窗口:

    北京时间2020年10月14日 22:00 - 2020年10月15日 04:00

    北京时间2020年10月21日 22:00 - 2020年10月22日 04:00

    北京时间2020年10月28日 22:00 - 2020年10月29日 04:00

    升级内容:所有地域的MQ服务(包含TCP、MQTT、HTTP接入方式)。

    升级影响:升级期间MQ控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端会自动重试机制,一般不会影响业务,但会有异常日志。

    升级期间,消息可能会有重复,应用应该按最值实践,做好消息的幂等;同时可能会有消息延迟的现象。如需在控制台进行管理操作,请避开维护时间段。HTTP接入可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。同时,您可使用监控管理功能对重要业务进行监控,具体设置方法请点击MQ控制台左侧监控报警菜单。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【升级】10月13日Donuts注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Donuts注册局维护通知】

    维护时间:北京时间2020年10月13日 20:00-22:00

    维护内容:接到注册局的通知,注册局 / 注册商将于上述时间对后台系统进行维护升级。

    维护影响:届时 .chat 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】堡垒机升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【堡垒机】【升级通知】

    1、升级窗口:2020年10月10日-11月30日 ;

    2、升级区域:升级国内站、国际站区域

    3、升级内容:

    堡垒机产品V3.2.10版本升级至V3.2.13版本,V3.2.13版本在系统架构、用户/主机配置、双因子手机号、支持语言等均进行了优化升级:  

    1)系统架构:支持高可用的双节点版本,提供带宽扩展包

    2)主机/用户配置:新建主机或用户可直接指定到相应的组

    3)用户向导:新增用户向导功能,更迅速的配置使用

    4)双因子手机号新增德国、澳洲、美东、美西、迪拜、东京、英国、印度、中国澳门的手机号码

    5)主机配置:标记已经释放的ecs,详细区分主机账号的报错信息

    6)维护功能:支持tcp端口检测

    4、升级方式:系统自动升级到最新版本

    5、升级影响:需要您提前断开正在运维的会话,保存会话数据

    ]]>
    【其他】8月28日边缘容器服务商业化通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【边缘容器服务】【商业化通知】

    商业化时间:

    北京时间2020年8月28日 14:30
    商业化内容:

    阿里云边缘容器服务已于2020年8月28日正式转商用化。

    边缘容器服务基于标准Kubernetes运行环境,提供Kubernetes 集群云端托管、边缘计算业务高度自治的能力。通过将边缘算力快速接入、统一管理、统一运维,轻松实现云边一体化协同的容器应用交付、运维和管控。

    商用后收费模式请参考产品计费文档:https://help.aliyun.com/document_detail/178718.htm

    关于边缘容器服务:https://help.aliyun.com/document_detail/124723.html


    ]]>
    【其他】9月29日ESSD PL0规格云盘商业化通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【ESSD PL0】【商业化通知】

    阿里云ESSD PL0规格云盘已经结束公测,于2020年9月29日正式商业化上线。此次上线的ESSD PL0规格云盘拥有最低百微秒时延,最大10000的IOPS,并且支持无损变配至ESSD系列其他规格。详细功能介绍以及价格信息请参考官网介绍

    ]]>
    【其他】10月23日ACK Pro商业化通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【ACK Pro】【商业化】

    商业化时间:

    北京时间2020年10月23日00:00

    商业化内容:

    容器服务ACK Pro版集群将于2020年10月23日00:00正式转为商用。针对许多对于生产环境有着高稳定性和高安全性要求的企业客户,ACK Pro版集群在ACK托管版集群的基础上进一步增强了可靠性、安全性,并且提供可赔付的SLA。

    转商用后定价计费请点此查看

    了解ACK Pro请点此查看


    ]]>
    【其他】商标局国庆/中秋期间服务器维护停止商标审核/递交申请通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【商标】【商标局国庆/中秋期间服务器维护停止商标审核/递交申请通知】

    维护时间:北京时间2020年10月1日-8日

    维护内容:因商标局国庆/中秋假期服务器维护,阿里云商标服务将于北京时间2020年10月1日-8日停止商标订单审核、递交商标申请。

    维护影响:阿里云商标服务将于2020年9月30日16:00停止递交商标注册申请,届时对您产生的影响包括:

    1、2020年9月30日16:00之后审核通过的订单将顺延至2020年10月9日递交;

    2、2020年9月30日递交的申请回执(申请号)将顺延至2020年10月9日后同步到订单中;

    3、2020年9月30日提交审核的商标订单将顺延至2020年10月9日开始审核

    在此期间购买阿里云商标服务、填写订单、支付费用及补齐材料等不受影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【其他】OpenAPI相关服务IP变更计划通知 Fri, 20 Jun 2025 02:20:33 +0800 【阿里云】【OpenAPI】【变更通知】

    变更窗口:

    北京时间2020年10月20日 00:00 - 2020年11月1号00:00

    变更内容:华北1(青岛)、华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、华东2金融云、华南1金融云、华北2政务云、香港、亚太东南1(新加坡)、亚太东南2(悉尼)、亚太东南5(雅加达)、亚太南部1(孟买)、中东东部1(迪拜)、欧洲中部1(法兰克福)、美国东部1(弗吉尼亚)、美国西部1(硅谷)、英国(伦敦)等地域的OpenAPI服务IP变更。

    变更影响:OpenAPI旧的的服务IP将下线不可用,如果您所负责的应用或安全策略中有涉及到硬编码阿里云OpenAPI的服务IP,请在变更前及时修改,以免变更后影响应用的正常运行。

    给您带来的不便敬请谅解,有任何问题,可随时通过工单或服务电话95187联系反馈。

    特别提醒:OpenAPI各服务的IP不定时会有变动,请不要以任务形式绑定固定IP,包括(不限于)自定义DNS、添加Hosts绑定、在安全组策略中绑定等行为,以免IP变动时对业务造成影响

    ]]>
    【升级】9月20日 .COM/.NET域名注册局系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【注册局维护通知】

    维护时间:北京时间2020年9月20日 09:00 - 09:45

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .com/.net 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】9月22日MMX注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【MMX注册局维护通知】

    维护时间:北京时间 2020年9月22日 13:00 - 15:00

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .Vip/.Work/.Beer/.Luxe/.Fit/.Yoga 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】9月29日Donuts注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Donuts注册局维护通知】

    维护时间:北京时间2020年9月29日 01:00-2:30

    维护内容:接到注册局的通知,注册局 / 注册商将于上述时间对后台系统进行维护升级。

    维护影响:届时,您的 .ltd/.group/.pub/.live/.rocks/.band/.market/.software/.social/.lawyer/.engineer/.news/.video/.studio/.today /.plus/.world/.run/.show/.city/.gold/.today/.cool/.zone/.chat/.company/.企业/.游戏 等域名的续费、转入转出、信息修改和过户域名等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的续费、转入、转出域名等操作在支付费用后状态为“处理中”,且可能出现“不成功”等状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    如您的业务操作失败,建议维护后再次尝试。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】9月25日消息队列AMQP升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列AMQP】【升级通知】

    升级窗口:北京时间2020年9月25日 00:00 -  03:00

    升级内容:华北1(青岛)、华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、香港等全部地域(及铂金版)的服务升级。

    升级影响:升级期间消息队列AMQP相关服务访问可能会出现多次闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过 5 分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【漏洞预警】FastAdmin 远程代码执行0day漏洞 Fri, 20 Jun 2025 02:20:33 +0800

    2020年9月22日,阿里云应急响应中心监测到FastAdmin爆发远程代码执行0day漏洞,黑客登录前台会员中心,即可远程GetShell,风险极大。


    漏洞描述

    FastAdmin是一款基于ThinkPHP和Bootstrap的后台开发框架、开放会员中心的站点,上传特定文件可直接GetShell。阿里云应急响应中心提醒FastAdmin用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    全版本(截止2020年9月22日官方暂未发布安全补丁或修复版本)


    漏洞评级

    严重


    安全建议

    1、关闭站点会员中心功能,在/application/config.php文件中,设置'usercenter' => false

    2、暂时关闭文件上传功能


    相关链接

    https://github.com/karsonzhang/fastadmin/issues/73



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单联系反馈。

    阿里云应急响应中心

    2020.9.22

    ]]>
    【漏洞预警】Linux内核AF_PACKET内存破坏导致权限提升漏洞(CVE-2020-14386) Fri, 20 Jun 2025 02:20:33 +0800

    近日,阿里云应急响应中心监测到Openwall社区披露一个Linux内核AF_PACKET原生套接字内存破坏漏洞,该漏洞出现在net/packet/af_packet.c中,由整数溢出导致越界写,可以通过它进行权限提升。该漏洞危害评级为高,编号为CVE-2020-14386。


    漏洞描述

    Linux发行版高于4.6的内核版本net/packet/af_packet.c中,在处理AF_PACKET时存在整数溢出漏洞,可以通过它进行权限提升。阿里云应急响应中心提醒用户尽快采取安全措施阻止漏洞攻击。


    受影响Linux发行版系统

    1、Ubuntu Bionic (18.04) 及后续的版本

    2、Debian 9/10

    3、CentOS 8/RHEL 8


    漏洞评级

    CVE-2020-14386  高危


    安全建议

    1、升级内核至安全版本

    2、禁用CAP_NET_RAW功能

    3、阿里云云安全中心Linux软件漏洞模块已支持对该漏洞一键检测和修复,详情登陆云安全中心





    相关链接

    https://www.openwall.com/lists/oss-security/2020/09/03/3

    https://github.com/cgwalters/cve-2020-14386

    https://sysdig.com/blog/cve-2020-14386-falco/



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单联系反馈。

    阿里云应急响应中心

    2020.9.22

    ]]>
    魔橙科技赋能商业银行,推动区块链金融场景应用落地-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在魔橙科技CEO陈敏涛看来,区块链技术是以数据加密、时间戳和分布式共识算法为依托,实现链式存储、智能合约和隐私保护等高级功能的分布式账本技术。区块链技术更像数据库和操作系统,是IT基础产业,是下一代互联网的基础设施,是一个未来技术的发展路径。快速发展的区块链技术被认为是可以用于解决新一代互联网价值交换问题以及网络传输的信用问题。

    他认为,区块链技术并非单纯点对点和去中心化。区块链技术的出现会让整个传统互联网平台更加可信、更加透明、更加公平。目前使用区块链技术提供的应用程序,更加互联网化,更加强调公平与双向激励,方便每个人参与进来,分享生态的成果,这才是最好的商业模式。

    魔橙在应用场景中实现有效“信用传递”
    区块链技术的落地就是在一个没有中心的业务模式中,帮助多方建立跨主体的信任环境,否则区块链技术可能并不是最好的解决方案。实际上,区块链技术是一个由所有节点共同维护、共同记账的“公共大账本”。

    魔橙科技独创的联盟链底层技术,结合了多个共识算法优势,实现可伸缩的网络节点准入机制,允许多重共识组合相互切换。极大降低区块链网络开发成本,使得商用网络在构建及运营阶段更加灵活可控,规避了因需求调整带来的潜在风险。

    核心模块包含:高性能共识算法,可伸缩节点,跨共识引擎及链上治理机制等
    魔橙联盟链底层技术.png

    通过区块链底层混沌系统,能够确保参与联盟链共建节点的完全可控性,保证联盟链整体运营的稳定。链上数据公开,联盟见证者可通过区块链浏览器对所有链上数据查询,保证了联盟链的数据公开完整及数据公平。在实际企业项目中,魔橙联盟链能够基于丰富行业应用经验,并结合联盟链的底层技术,以及数据隐私保护和多方安全技术等应用框架的构建,适用于想通过区块链改进商业应用性能的商业需求。

    与银行如何擦出火花?
    金融一直被认为是区块链最合适的落地场景之一,在魔橙的合作方中有不少是大型银行、城商行以及券商。

    在说到区块链应用的细分赛道时,陈敏涛坦言,供应链金融对魔橙来说是很重要的一个主推方向。2020年年初,魔橙参与服务世界500强商业银行的项目中。在该项目中,魔橙提供了结合业务应用场景的整体解决方案,在根据银行单一应用场景细化实现方式。因为是创新技术在金融领域的试点,项目采用敏捷方式开展实施,快速迭代如期交付。(因合作方要求,对于合作银行名字予以保密)

    据魔橙科技负责该项目的总监黄洋表示,在这个项目中,魔橙的主要作用是协同银行,基于“区块链+”模式重构银行生态系统,构建新型的区块链金融服务业务模式和拓展商业银行多类应用场景拓展。目前项目搭建基于区块链技术的微服务控制台系统,包含区块链服务接口、合约网关、安全合规、链上账户管理、智能合约管理;通用服务、可信存证、交易查询中心、通用监控,前端控制台等模块。

    “我们提供的是基础设施,让客户基于我们的服务,去做自己的金融服务,目的是让他们用魔橙的区块链。同时,供应链金融一个很大的特性是参与方越多越好,而且不同参与方的链与链之间是有交集的,最终市场将形成网状。”黄洋表示,魔橙的愿景是,通过底层,把所有的核心企业、上下游企业、金融机构,包括保理,券商之类的企业链接在一起,他们做他们的业务,我们降低他们的交易和信任成本。只要规模做大了,就是魔橙的诉求。
    **
    魔橙的目标和定位是什么**

    区块链是个基础设施,最终市场上同类企业不会太多,做底层技术的也不会有几家,但是在这个基础设施上做应用的公司非常多。我们要把基础设施做好,目的是让其他业务公司用我们的区块链。

    每个行业都是有门槛的,但是以技术为依托,我们会有很多合作伙伴,魔橙会和行业专家一起做事情,争取行业的支持。2020年7月,魔橙与复旦大学、上海海事大学、上海海洋大学联合研发推出“跨境贸易风险监测与可信溯源”平台正式上线。

    接下来,魔橙科技将与更多金融机构共建跨产业、跨机构的数字经济新模式,实现真正意义上的产业数字化。

    ]]>
    阿里云小程序PHP环境怎么搭建-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 最近想着阿里云能不能搭建小程序官方的demo,之前用过基于腾迅云的小程序解决方案,虽然很好,但夸何没有在腾迅云注册备案过域名,而曾经在阿里云上注册备案过域名.本文主要和大家分享阿里云如何搭建小程序PHP环境,希望能帮助到大家。image.png
    现在立即选购赠送3000元大礼包

    基本环境 CentOS 7.3
    (一)安装 Nginx

    yum -y install nginx

    查看是否安装成功

    nginx -v

    如果安装成功则显示

    (二)安装 PHP
    Wafer 的 Demo 需要 5.6 以上版本的 PHP,添加 remi 源.

    wget 'https://mirrors.tuna.tsinghua.edu.cn/remi/enterprise/remi.repo' -O /etc/yum.repos.d/remi.repo

    查看是否安装成功

    php -v

    php版本要大于5.6
    (三)配置 Nginx 和 HTTPS
    申请一个 SSL 证书,可以到阿里云申请免费的 SSL 证书,申请成功之后下载证书,并把压缩包中 Nginx 目录下的证书文件上传到服务器的 /data/release/nginx 目录,如果没有这个目录则新建:上传完证书以后,配置 Nginx,进入服务器的 /etc/nginx/conf.d 目录,新建一个 weapp.conf 文件,内容为以下,注意(www.xx.com改为自己的域名,1_www.xx.com_budle.crt和2_www.xx.com.key分别改为自己的证书文件)

    # 重定向 http 到 https
    
    www.xx.com
    server { listen 80; server_name www.xx.com; rewrite ^(.*)$ https://$server_name$1 permanent;}server { listen 443; server_name www.xx.com; ssl on; ssl_certificate /data/release/nginx/1_www.xx.com_bundle.crt; ssl_certificate_key /data/release/nginx/2_www.xx.com.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA; ssl_session_cache shared:SSL:50m; ssl_prefer_server_ciphers on; root /data/release/php-demo; location ~ .php$ { root /data/release/php-demo; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location /weapp/ { root /data/release/php-demo; index index.html index.htm index.php; try_files $uri $uri/ /index.php; }}

    运行nginx

    nginx -t

    (四)安装mysql
    安装mysql5.7

    1、配置YUM源

    下载mysql源安装包

    wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm

    安装mysql源

    yum localinstall mysql57-community-release-el7-8.noarch.rpm

    检查mysql源是否安装成功
    yum repolist enabled | grep "mysql.-community."
    image.png

    2、安装MySQL

    yum install mysql-community-server

    3、启动MySQL服务

    systemctl start mysqld

    查看MySQL的启动状态
    shell> systemctl status mysqld
    image.png

    4、开机启动

    systemctl enable mysqld
    systemctl daemon-reload

    5、修改root本地登录密码

    mysql安装完成之后,在/var/log/mysqld.log文件中给root生成了一个默认密码。通过下面的方式找到root默认密码,然后登录mysql进行修改:
    grep 'temporary password' /var/log/mysqld.log

    登陆并修改默认密码
    mysql -u root -p

    mysql>ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码!';

    新建一个数据库名为 cAuth,排序规则为 utf8mb4_unicode_ci,小程序后台用到
    mysql>CREATE DATABASE IF NOT EXISTS cAuth,排序规则为 DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
    image.png
    (五)上传 Demo 和启动
    到 wafer2-quickstart-php 仓库下载最新的 Demo 代码,修改 server/config.php:

    <?php/**

    Wafer php demo 配置文件
    */$config = [ 'rootPath' => '', // 微信小程序 AppID

    'appId' => '', // 微信小程序 AppSecret
    'appSecret' => '', // 使用腾讯云代理登录
    'useQcloudLogin' => false, //不使用腾迅云代理登录 /**

    这里请填写云数据库的
    */
    'mysql' => [ 'host' => 'localhost', 'port' => 3306, 'user' => 'root', 'db' => 'cAuth', 'pass' => '数据库密码', 'char' => 'utf8mb4'
    ], 'cos' => [ /**

    区域上海:cn-east广州:cn-sorth北京:cn-north广州二区:cn-south-2成都:cn-southwest新加坡:sg@see https://cloud.tencent.com/document/product/436/6224
    */

    'region' => 'cn-sorth', // Bucket 名称

    'fileBucket' => 'wafer', // 文件夹
    'uploadFolder' => ''

    ], // 微信登录态有效期
    'wxLoginExpires' => 7200, 'wxMessageToken' => 'abcdefgh', // 其他配置
    'serverHost' => 'wx.wafersolution.com', 'tunnelServerUrl' => 'http://tunnel.ws.qcloud.la', 'tunnelSignatureKey' => '27fb7d1c161b7ca52d73cce0f1d833f9f5b5ec89', // 腾讯云相关配置可以查看云 API 秘钥控制台:https://console.cloud.tencent.com/capi
    'qcloudAppId' => 1200000000,// 必须是数字
    'qcloudSecretId' => '你的腾讯云 SecretId', 'qcloudSecretKey' => '你的腾讯云 SecretKey', 'networkTimeout' => 30000];

    接着将 server 目录下的所有文件都上传到 /data/release/weapp/php-demo 目录下:

    相关推荐:

    linux下apache重启并查看php环境

    PHP环境配置

    LNMP环境搭建(一)搭建PHP环境

    以上就是阿里云如何搭建小程序PHP环境的详细内容。

    云服务器ECS地址:链接地址
    阿里云2000元通用代金券:点击领取

    ]]>
    9.25直播预告|如何0基础获得Apache Cassandra Administrator国际认证?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本周五(9月25号)下午4点不见不散哦~
    • 本期直播主题:如何0基础获得Apache Cassandra Administrator国际认证?
    • 直播时间:9月25号(周五)16:00-17:00
    • 直播讲师:米诺|阿里云NoSQL数据库产品专家
    • 直播简介:Apache Cassandra在宽表数据库流行度中持续8+年排第一,已成为国内外流行度最高的宽表数据库。本次技术直播将为您分享0基础拿Cassandra Administrator国际认证的考试经验。

    参与方式:

    钉钉扫描下方海报二维码进群观看直播
    米诺.png

    ]]>
    传统服务器和ECS区别-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 传统服务器:

    金钱成本 服务周期

    资源限制 人力投入

    阿里云ECS服务器:

    根据企业运行环境按需购买

    数据多次备份

    超A级数据中心--双路独市电,三路网络,N+1柴油发电机后备电源

    自动化运维自动迁移到其他物理机 ----稳定性和联系性

    云盾----安全防护

    将企业数据库资源,存储资源,计算资源打通,连接资源孤岛,应用孤岛,数据孤岛,全局快速进行市场洞察

    ]]>
    ECS训练营-第一天-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 ECS训练营-第一天

    今天完成了6个课程,首先了解了什么是ECS(elastic compute service),然后创建了服务器实例,安装CentOS系统,用ssh远程连接了系统,完成了Apache、SQL的安装。
    image.png

    ]]>
    Class 4 使用PolarDB和ECS搭建门户网站-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 使用PolarDB和ECS搭建门户网站

    1.创建资源

    1. 在页面左侧,单击 云产品资源 下拉菜单,查看本次实验资源。
    2. 单击 免费开通 创建所需资源。

    1-01.png

    资源创建过程需要1~3分钟。完成实验资源的创建后,您可以在 云产品资源 列表查看已创建的资源信息,例如:IP地址、用户名和密码等。

    2.创建PolarDB数据库账号

    1. 单击页面左侧 云产品资源 > 一键复制登录url

    4-02.png

    1. 打开浏览器隐身窗口(无痕模式),粘贴已复制的url地址前往 RAM用户登录 界面,登录 阿里云管理控制台

    以Chrome浏览器为例,打开新的无痕窗口,登录 阿里云管理控制台

    1. 依次单击更多>打开新的无痕窗口。
    2. 在地址栏粘贴登录url,访问 RAM用户 登录页面
    3. 在登录用户名称处,输入 子用户名称 ,单击 下一步
    4. 输入密码,单击 登录 进入 阿里云管理控制台
    1. 阿里云控制台首页 左侧导航栏,依次单击 产品与服务 > 云数据库PolarDB ,进入 云数据库PolarDB管理控制台

    4-03.png

    1. 单击左侧 集群列表 ,然后选择云产品资源提供的地域。例如:华东2(上海)

    4-04.png

    1. 创建数据库账号。

      1. 集群列表 页面,单击 集群ID ,进入 集群详情界面

    4-05.png

    1. 单击左侧导航栏 配置与管理 > 账号管理
    2. 单击左上方 创建账号

    4-06.png

    1. 参考说明配置账号信息,然后单击 确定

    4-07.png

    • 数据库账号:输入数据库账号名称,例如:test_user 。
    • 账号类型:此处选择普通账号。
    • 密码:设置账号密码,例如:Password1213。
    • 确认密码:再次输入密码。
    1. 创建数据库。

      1. 在实例详情页,单击左侧导航栏的 数据库管理 ,然后单击 创建数据库

    4-08.png

    1. 参考说明配置数据库信息,然后单击 创建

    4-09.png

    • 数据库(DB)名称:输入数据库名称,例如:pbootcms 。
    • 支持字符集:默认设为utf8mb4。
    • 授权账号:选择上一步创建的数据库账号test_user。
    • 账号类型:默认设置为读写。
    • 备注说明:非必填。用于备注该数据库的相关信息,便于后续数据库管理,最多支持256个字符。
    1. 设置数据库白名单。

    连接数据库需要设置数据库白名单,点击 [集群白名单],然后点击 [设置] 设置数据库集群白名单。

    4-10.png

    在白名单界面将默认的白名单地址127.0.0.1更改为 0.0.0.0/0,然后点击 [确定] 使白名单地址生效。

    4-11.png

    连接ECS服务器

    1. 打开终端工具。
    • Windows:打开命令窗口。
    • MAC:打开命令行终端Terminal。

    Windows用户请检查系统中是否安装有SSH工具。检查方法:

    在终端中输入命令 ssh -V 。

    ssh -V

    如果显示SSH版本则表示已安装,如下图所示。

    1-02.png

    如果未安装,请下载安装 OpenSSH工具。

    1. 在终端中输入连接命令 ssh [username]@[ipaddress] 。

    您需要将其中的username和ipaddress替换为步骤一中创建的ECS服务器的弹性公网IP。例如:

    ssh root@123.123.123.123

    4-12.png

    命令显示结果如下:

    2-03.png

    1. 输入 yes。
    2. 同意继续后将会提示输入登录密码。 密码为已创建的云服务的ECS的登录密码。

    2-04.png

    登录成功后会显示如下信息。

    2-05.png

    安装LAMP环境

    LAMP是指运行在Linux下的Apache、MySQL和PHP的环境。参考以下操作在云服务器上安装开发环境。

    1. 在ECS服务器上,执行以下命令安装Apache服务及其扩展包。
    yum -y install httpd httpd-manual mod_ssl mod_perl mod_auth_mysql

    返回类似如下图结果则表示安装成功。

    4-13.png

    1. PbootCMS是使用PHP语言开发的CMS系统。参考以下操作安装PHP环境。

    执行以下命令,安装PHP。

    yum -y install php php-mysql gd php-gd gd-devel php-xml php-common php-mbstring php-ldap php-pear php-xmlrpc php-imap
    1. 执行以下命令下载并安装MySQL。
    wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
    yum -y install mysql57-community-release-el7-10.noarch.rpm
    yum -y install mysql-community-server
    1. 执行以下命令启动MySQL数据库。
    systemctl start mysqld

    搭建门户网站

    在完成环境部署后,参考以下操作搭建门户网站。

    1. 在ECS服务器上,执行以下命令,安装Git。
    yum -y install git
    1. 在ECS服务器上,执行以下命令下载PbootCMS源码文件。
    cd ~ && git clone https://gitee.com/hnaoyun/PbootCMS.git
    1. 执行以下命令将安装包拷贝到Apache的wwwroot目录下。
    cp -r PbootCMS/* /var/www/html/
    1. 执行以下命令修改站点根目录文件权限。
    chmod -R a+w /var/www/html
    1. 向数据库中导入CMS的初始数据。

    执行以下命令初始化数据库pbootcms的表结构和数据。

    说明: 在执行命令前,请修改一下三个参数。

    • 数据库连接地址参见集群详情页面下方链接地址板块。
    • test_user为步骤二中创建的数据库账号。
    • Password1213步骤二中创建的数据库密码。
    sql_file="/var/www/html/static/backup/sql/"$(ls /var/www/html/static/backup/sql/) &&
    mysql -h数据库连接地址 -utest_user -pPassword1213 -Dpbootcms < $sql_file
    1. 执行以下命令,修改CMS系统数据库配置。

    说明: 在执行命令前,请根据参数说明替换您的数据库配置。

    cat > /var/www/html/config/database.php << EOF
    <?php
    return array(
        'database' => array(
            'type' => 'mysqli', // 数据库连接驱动类型: mysqli,sqlite,pdo_mysql,pdo_sqlite
            'host' => '数据库连接地址', // PolarDB数据库链接地址
            'user' => 'test_user', // PolarDB数据库的用户名
            'passwd' => 'Password1213', // PolarDB数据库的密码
            'port' => '3306', // 数据库端口
            'dbname' => 'pbootcms' //数据库名称
        )
    );
    EOF
    1. 返回ECS控制台,在ECS实例列表页面,单击已创建的ECS实例ID链接进入ECS详情页。
    2. 在左侧导航栏,单击 本实例安全组 ,然后单击安全组的ID链接查看安全组配置。

    确保安全组开放了80端口访问,否则无法访问已搭建的门户网站。安全组是一种虚拟防火墙,具备状态检测和数据包过滤能力,用于在云端划分安全域。通过配置安全组规则,您可以控制安全组内一台或多台ECS实例的入流量和出流量。

    4-14.png

    1. 访问程序。

    执行以下命令重启 Apache服务。

    systemctl restart httpd

    在浏览器地址栏输入云服务器的公网IP地址,进入门户网站首页

    系统后台默认访问路径为http://<ECS公网IP地址>/admin.php。默认账号为admin,密码为123456

    至此您已完成门户网站的搭建,您可以根据公司的需求自定义门户网站的内容。

    4-15.png

    ]]>
    Class 5 搭建个人Leanote云笔记本-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 搭建个人Leanote云笔记本

    本教程将介绍如何搭建个人Leanote云笔记本。

    场景体验目标

    本场景将提供一台配置了CentOS 7.7的ECS实例(云服务器)。您可以参考本教程的操作基于已有的环境搭建一个Leanote云笔记本。

    背景知识

    Leanote是一款在线的云笔记应用,有如下特点:

    • 支持网页、PC、手机APP客户端和微信版,随时记录,方便分享,支持语音,图片输入。
    • 代码高亮,涵盖所有主流语言的代码高亮,随心所欲在Leanote里写代码,记知识。
    • Markdown 编辑器,实时同步预览。
    • 专业数学公式编辑,像Word和Latex能编辑数学公式。
    • 支持创建思维脑图,将散乱的想法以树状信息分层展示。
    • 详细历史纪录,每次保存都在后端备份,轻松查找,一键恢复。
    • 实时同步云端。

    1.创建资源

    1. 请点击页面左侧的 云产品资源,在下拉栏中,查看本次实验资源信息;
    2. 在资源下拉栏点击 免费开通 按钮,开始创建实验资源。

    1-01.png

    说明:资源创建过程需要1-3分钟。完成实验资源的创建后,用户可以通过 云产品资源 查看实验中所需的资源信息,例如:IP地址、用户名、密码等。

    2.连接ECS服务器

    1. 打开系统自带的终端工具。
    • Windows:CMD或Powershell。
    • MAC:Terminal。
    1. 在终端中输入连接命令ssh [username]@[ipaddress]。您需要将其中的username和ipaddress替换为第1小节中创建的ECS服务器的登录名和公网地址。例如:
    ssh root@123.123.123.123

    5-02.png

    命令显示结果如下:

    2-03.png

    1. 输入yes。
    2. 同意继续后将会提示输入登录密码。 密码为已创建的云服务的ECS的登录密码。

    2-04.png

    登录成功后会显示如下信息。

    2-05.png

    3.安装MongoDB

    MongoDB是一个基于分布式文件存储的高性能数据库,介于关系数据库和非关系数据库之间,它支持的数据结构非常松散是类似于json和bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

    Leanote云笔记使用MongoDB作为后端数据库,按照以下步骤按照MongoDB数据库。

    1. 执行以下命令,安装MongoDB。
    yum -y install mongodb mongodb-server.x86_64 mariadb-devel.i686
    1. 执行以下命令,启动MongoDB服务。
    systemctl start mongod
    1. 执行以下命令,查看MongoDB运行状态。
    systemctl status mongod

    5-03.png

    4.安装Leanote

    1. 下载Leanote二进制安装包。
    wget https://nchc.dl.sourceforge.net/project/leanote-bin/2.6.1/leanote-linux-amd64-v2.6.1.bin.tar.gz
    1. 解压安装包。
    tar -zxvf leanote-linux-amd64-v2.6.1.bin.tar.gz
    1. 编辑文件leanote/conf/app.conf,在文件中找到app.secret项,将该项的值改为任意字符串。(如不修改将会有安全风险)。

    说明: 根据Leanote官方文档,如不修改app.secret项的值,将会有安全隐患。

    1. 使用vim编辑器打开文件leanote/conf/app.conf。
    vim leanote/conf/app.conf
    1. 进入vim编辑器后,输入:/app.secret=并按下回车查找app.secret位置。
    2. 找到该项位置后按下i键进入编辑模式,修改该项的值为任意字符串。
    3. 修改完成后,按下esc键退出编辑模式,输入:wq保存并退出vim编辑器。

    修改后如图所示。

    5-04.png

    1. 初始化数据库。
    mongorestore -h localhost -d leanote --dir /root/leanote/mongodb_backup/leanote_install_data/
    1. 启动服务。
    nohup bash /root/leanote/bin/run.sh > /root/leanote/run.log 2>&1 &
    1. 访问云笔记。

    在浏览器中访问http://<ECS公网地址>:9000,请将<ECS公网地址>替换为左侧资源栏中的ECS公网IP地址。默认管理用户为admin,密码为abc123。登录成功后如下图所示。

    5-05.png

    ]]>
    ECS七天训练营入门笔记(1)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 使用云计算的优势:
    1 无需采购硬件设备,按需购买资源;
    2 拥有至少50多种操作系统,轻松实现LAMP系统或者windows服务器;
    3购买简单配置灵活。
    阿里云计算的优点:
    1 多层次的备份;
    2 多路供电保证服务器无断电;
    3 云盾安全防护保障服务器安全;
    4 将数据库、存储和计算资源统一。

    ]]>
    ECS学习心得1-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 传统服务器具有价格高昂,采购周期长,资源闲置浪费率高,维护成本高的缺点。

    阿里云ECS覆盖50多款操作系统,包含开源LAMP组合和常见的Windows平台。

    包含传统x86服务器及GPU和FPGA的异构计算,包含通用型,内存型,网络增强型,本地SSD型和计算型,甚至企业级Oracle数据库所需的超大规格独占物理机,配置及使用更为灵活。

    ECS在底层对每份数据进行多次备份,物理层面拥有超A级数据中心,通过双路独市电引入、三路网络和N+1柴油发电机后备电源确保数据安全。

    ECS自动化运维将数据迁移到其他物理机,将数据恢复到故障前最后状态,屏蔽硬件故障同时保证业务稳定和连续。

    ECS可在底层打通企业数据库资源,存储资源和计算资源,联通传统IT烟囱式架构下的资源孤岛,应用孤岛和数据孤岛,提高信息整合能力。

    ]]>
    Class 6 案例分享——钉钉-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Q:钉钉背后的技术架构是怎样的?应用了阿里云的哪些服务?
    A:钉钉作为一个企业级产品,使用了热门的平台技术SaaS。钉钉很多的设计都面向云去设计,这样产品可以随着云建设更快地部署,更多地适应客户需求。钉钉目前使用了ECS、OSS、OTS,未来还将用到Open Search。钉钉很多的数据都部署在阿里云的ECS上,因此数据也就存在RDS上,即阿里云的数据库,这些数据的安全,整体服务的可靠十分重要,阿里云已有的基础设施,符合钉钉对稳定性,对性能的要求。
    Q:可否具体谈谈使用云计算后为钉钉解决问题或者带来的价值?
    A:钉钉上有一个非常受客户喜欢的应用叫日志,通过日志。员工每天可以发日报、周报,也可以发一些月报,钉钉的模板是可以定制化的,适合每家公司对日志的要求。日志的整体服务,实际上就部署在阿里云的ECS,这是我们和一家ISV共同开发的。如果没有阿里云,那整个数据的安全性,以及整体服务的稳定性都存在疑问,随着用户量不断增大,整个架构能不能水平扩容,都会有很大的担忧。
    Q:企业级用户对应用的安全性有更高的要求,钉钉如何介入阿里云保障企业信息安全?
    A:我们把安全作为钉钉的一个重要功能来设计和保障,首先从信息的存储、传输,无论是在客户端的存储还是在服务端的存储,我们都采用了最高的加密程序,对它进行加密,首先我们阿里云的基础设施安全,提供了很周全的防护,包括黑客攻击。包括DDoS攻击等,保障了网站的稳定性,阿里云这些安全方面的产品,实际上给钉钉节省了很多工作,我们不需要再担心主机被第三方,破解账号登录的问题了,我们能花费更多的时间去考虑怎样让我们的产品变得更安全。

    ]]>
    在ECS上部署门户网站-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800
  • 根据PolarDB官方文档创建一个数据库集群,并设置白名单为ECS IP.
  • 然后在ECS上使用LAMP一键安装,安装的时候选择MySQL5.7版本的数据库.
  • 通过git下载PbootCM的源码并放到Apache的wwwroot目录下,并对这些文件加上所有人都可以写的权限.
    然后导入CMS的初始数据:
  • $ sql_file="/var/www/html/static/backup/sql/"$(ls /var/www/html/static/backup/sql/) &&
    mysql -h数据库连接地址 -utest_user -pPassword1213 -Dpbootcms < $sql_file
    1. 修改CMS的数据库配置,使用PolarDB数据库:
    $ cat > /var/www/html/config/database.php << EOF
    <?php
    return array(
        'database' => array(
            'type' => 'mysqli', // 数据库连接驱动类型: mysqli,sqlite,pdo_mysql,pdo_sqlite
            'host' => '数据库连接地址', // PolarDB数据库链接地址
            'user' => 'test_user', // PolarDB数据库的用户名
            'passwd' => 'Password1213', // PolarDB数据库的密码
            'port' => '3306', // 数据库端口
            'dbname' => 'pbootcms' //数据库名称
        )
    );
    EOF
    1. 在ECS控制台上开放80端口,然后重启apache服务,即可通过ECS IP访问到该门户网站.
    ]]>
    wordpress 网站安装主题-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1、在主题库中直接搜索安装。

    2、上传主题包,安装主题。

    3、安装主题碰到的问题

    (1)、上传的文件尺寸超过upload_max_filesize文件中定义的php.ini值.

            解决方法:
            修改/etc/php.ini文件中的   
    
            post_max_size = 20M
            upload_max_filesize = 20M
            然后保存,重启一下apache服务。
    

    (2)、Wordpress上传文件提示“无法建立目录wp-content/uploads/2020/09。有没有上级目录的写权限?”

           解决方法:找到wordpress数据库,打开wp_options表,修改表中键名为upload_path的键值 这个值是一个路径,里面是空的(也可能不是空的),添加 “wp-content/uploads” 就解决了。 ===双引号必加!类似于注册表键值
    ]]>
    阿里云服务器选择及使用体验!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    用图机器学习探索 A 股个股相关性变化-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在本系列的前文 [1,2]中,我们介绍了如何使用 Python 语言图分析库 NetworkX [3] + Nebula Graph [4] 来进行<权力的游戏>中人物关系图谱分析。

    在本文中我们将介绍如何使用 Java 语言的图分析库 JGraphT [5] 并借助绘图库 mxgraph [6] ,可视化探索 A 股的行业个股的相关性随时间的变化情况

    JGraphT

    数据集的处理

    本文主要分析方法参考了[7,8],有两种数据集:

    股票数据(点集)

    从 A 股中按股票代码顺序选取了 160 只股票(排除摘牌或者 ST 的)。每一支股票都被建模成一个点,每个点的属性有股票代码,股票名称,以及证监会对该股票对应上市公司所属板块分类等三种属性;

    表1:点集示例

    顶点id 股票代码 股票名称 所属板块
    1 SZ0001 平安银行 金融行业
    2 600000 浦发银行 金融行业
    3 600004 白云机场 交通运输
    4 600006 东风汽车 汽车制造
    5 600007 中国国贸 开发区
    6 600008 首创股份 环保行业
    7 600009 上海机场 交通运输
    8 600010 包钢股份 钢铁行业

    股票关系(边集)

    边只有一个属性,即权重。边的权重代表边的源点和目标点所代表的两支股票所属上市公司业务上的的相似度——相似度的具体计算方法参考 [7,8]:取一段时间(2014 年 1 月 1 日 - 2020 年 1 月 1 日)内,个股的日收益率的时间序列相关性 $P_{ij}$ 再定义个股之间的距离为 (也即两点之间的边权重):

    $$l_{ij} = sqrt{2(1-P_{ij})}$$

    通过这样的处理,距离取值范围为 [0,2]。这意味着距离越远的个股,两个之间的收益率相关性越低

    表2: 边集示例

    边的源点 ID 边的目标点 ID 边的权重
    11 12 0.493257968
    22 83 0.517027513
    23 78 0.606206233
    2 12 0.653692415
    1 11 0.677631482
    1 27 0.695705171
    1 12 0.71124344
    2 11 0.73581915
    8 18 0.771556458
    12 27 0.785046446
    9 20 0.789606527
    11 27 0.796009627
    25 63 0.797218349
    25 72 0.799230001
    63 115 0.803534952

    这样的点集和边集构成一个图网络,可以将这个网络存储在图数据库 Nebula Graph 中。

    JGraphT

    JGraphT 是一个开放源代码的 Java 类库,它不仅为我们提供了各种高效且通用的图数据结构,还为解决最常见的图问题提供了许多有用的算法:

    • 支持有向边、无向边、权重边、非权重边等;
    • 支持简单图、多重图、伪图;
    • 提供了用于图遍历的专用迭代器(DFS,BFS)等;
    • 提供了大量常用的的图算法,如路径查找、同构检测、着色、公共祖先、游走、连通性、匹配、循环检测、分区、切割、流、中心性等算法;
    • 可以方便地导入 / 导出 GraphViz [9]。导出的 GraphViz 可被导入可视化工具 Gephi[10] 进行分析与展示;
    • 可以方便地使用其他绘图组件,如:JGraphX,mxGraph,Guava Graphs Generators 等工具绘制出图网络。

    下面,我们来实践一把,先在 JGraphT 中创建一个有向图:

    import org.jgrapht.*;
    import org.jgrapht.graph.*;
    import org.jgrapht.nio.*;
    import org.jgrapht.nio.dot.*;
    import org.jgrapht.traverse.*;
    
    import java.io.*;
    import java.net.*;
    import java.util.*;
    
    Graph<URI, DefaultEdge> g = new DefaultDirectedGraph<>(DefaultEdge.class);

    添加顶点:

    URI google = new URI("http://www.google.com");
    URI wikipedia = new URI("http://www.wikipedia.org");
    URI jgrapht = new URI("http://www.jgrapht.org");
    
    // add the vertices
    g.addVertex(google);
    g.addVertex(wikipedia);
    g.addVertex(jgrapht);

    添加边:

    // add edges to create linking structure
    g.addEdge(jgrapht, wikipedia);
    g.addEdge(google, jgrapht);
    g.addEdge(google, wikipedia);
    g.addEdge(wikipedia, google);

    图数据库 Nebula Graph Database

    JGraphT 通常使用本地文件作为数据源,这在静态网络研究的时候没什么问题,但如果图网络经常会发生变化——例如,股票数据每日都在变化——每次生成全新的静态文件再加载分析就有些麻烦,最好整个变化过程可以持久化地写入一个数据库中,并且可以实时地直接从数据库中加载子图或者全图做分析。本文选用 Nebula Graph 作为存储图数据的图数据库。

    Nebula Graph 的 Java 客户端 Nebula-Java [11] 提供了两种访问 Nebula Graph 方式:一种是通过图查询语言 nGQL [12] 与查询引擎层 [13] 交互,这通常适用于有复杂语义的子图访问类型; 另一种是通过 API 与底层的存储层(storaged)[14] 直接交互,用于获取全量的点和边。除了可以访问 Nebula Graph 本身外,Nebula-Java 还提供了与 Neo4j [15]、JanusGraph [16]、Spark [17] 等交互的示例

    在本文中,我们选择直接访问存储层(storaged)来获取全部的点和边。下面两个接口可以用来读取所有的点、边数据:

    // space 为待扫描的图空间名称,returnCols 为需要读取的点/边及其属性列,
    // returnCols 参数格式:{tag1Name: prop1, prop2, tag2Name: prop3, prop4, prop5}
    Iterator<ScanVertexResponse> scanVertex(
                String space, Map<String, List<String>> returnCols);
    Iterator<ScanEdgeResponse> scanEdge(
                String space, Map<String, List<String>> returnCols);

    第一步:初始化一个客户端,和一个 ScanVertexProcessor。ScanVertexProcessor 用来对读出来的顶点数据进行解码:

    MetaClientImpl metaClientImpl = new MetaClientImpl(metaHost, metaPort);
    metaClientImpl.connect();
    StorageClient storageClient = new StorageClientImpl(metaClientImpl);
    Processor processor = new ScanVertexProcessor(metaClientImpl);

    第二步:调用 scanVertex 接口,该接口会返回一个 scanVertexResponse 对象的迭代器:

    Iterator<ScanVertexResponse> iterator =
                    storageClient.scanVertex(spaceName, returnCols);

    第三步:不断读取该迭代器所指向的 scanVertexResponse 对象中的数据,直到读取完所有数据。读取出来的顶点数据先保存起来,后面会将其添加到到 JGraphT 的图结构中:

    while (iterator.hasNext()) {
      ScanVertexResponse response = iterator.next();
      if (response == null) {
        log.error("Error occurs while scan vertex");
        break;
      }
      
      Result result =  processor.process(spaceName, response);
      results.addAll(result.getRows(TAGNAME));
    }

    读取边数据的方法和上面的流程类似。

    在 JGraphT 中进行图分析

    第一步:在 JGraphT 中创建一个无向加权图 graph:

    Graph<String, MyEdge> graph = GraphTypeBuilder
                    .undirected()
        .weighted(true)
        .allowingMultipleEdges(true)
        .allowingSelfLoops(false)
        .vertexSupplier(SupplierUtil.createStringSupplier())
        .edgeSupplier(SupplierUtil.createSupplier(MyEdge.class))
        .buildGraph();

    第二步:将上一步从 Nebula Graph 图空间中读出来的点、边数据添加到 graph 中:

    for (VertexDomain vertex : vertexDomainList){
        graph.addVertex(vertex.getVid().toString());
        stockIdToName.put(vertex.getVid().toString(), vertex);
    }
    
    for (EdgeDomain edgeDomain : edgeDomainList){
        graph.addEdge(edgeDomain.getSrcid().toString(), edgeDomain.getDstid().toString());
        MyEdge newEdge = graph.getEdge(edgeDomain.getSrcid().toString(), edgeDomain.getDstid().toString());
        graph.setEdgeWeight(newEdge, edgeDomain.getWeight());
    }

    第三步:参考 [7,8] 中的分析法,对刚才的图 graph 使用 Prim 最小生成树算法(minimun-spanning-tree),并调用封装好的 drawGraph 接口画图:

    普里姆算法(Prim's algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树。即,由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

    SpanningTreeAlgorithm.SpanningTree pMST = new PrimMinimumSpanningTree(graph).getSpanningTree();
    
    Legend.drawGraph(pMST.getEdges(), filename, stockIdToName);

    第四步:drawGraph 方法封装了画图的布局等各项参数设置。这个方法将同一板块的股票渲染为同一颜色,将距离接近的股票排列聚集在一起

    public class Legend {
      
    ...
      
      public static void drawGraph(Set<MyEdge> edges, String filename, Map<String, VertexDomain> idVertexMap) throws IOException {
         // Creates graph with model
         mxGraph graph = new mxGraph();
         Object parent = graph.getDefaultParent();
    
         // set style
         graph.getModel().beginUpdate();
         mxStylesheet myStylesheet =  graph.getStylesheet();
         graph.setStylesheet(setMsStylesheet(myStylesheet));
    
         Map<String, Object> idMap = new HashMap<>();
         Map<String, String> industryColor = new HashMap<>();
    
         int colorIndex = 0;
    
         for (MyEdge edge : edges) {
           Object src, dst;
           if (!idMap.containsKey(edge.getSrc())) {
             VertexDomain srcNode = idVertexMap.get(edge.getSrc());
             String nodeColor;
             if (industryColor.containsKey(srcNode.getIndustry())){
               nodeColor = industryColor.get(srcNode.getIndustry());
             }else {
               nodeColor = COLOR_LIST[colorIndex++];
               industryColor.put(srcNode.getIndustry(), nodeColor);
             }
             src = graph.insertVertex(parent, null, srcNode.getName(), 0, 0, 105, 50, "fillColor=" + nodeColor);
             idMap.put(edge.getSrc(), src);
           } else {
             src = idMap.get(edge.getSrc());
           }
    
           if (!idMap.containsKey(edge.getDst())) {
             VertexDomain dstNode = idVertexMap.get(edge.getDst());
    
             String nodeColor;
             if (industryColor.containsKey(dstNode.getIndustry())){
               nodeColor = industryColor.get(dstNode.getIndustry());
             }else {
               nodeColor = COLOR_LIST[colorIndex++];
               industryColor.put(dstNode.getIndustry(), nodeColor);
             }
    
             dst = graph.insertVertex(parent, null, dstNode.getName(), 0, 0, 105, 50, "fillColor=" + nodeColor);
             idMap.put(edge.getDst(), dst);
           } else {
             dst = idMap.get(edge.getDst());
           }
           graph.insertEdge(parent, null, "", src, dst);
         }
    
    
         log.info("vertice " + idMap.size());
         log.info("colorsize " + industryColor.size());
    
         mxFastOrganicLayout layout = new mxFastOrganicLayout(graph);
         layout.setMaxIterations(2000);
         //layout.setMinDistanceLimit(10D);
         layout.execute(parent);
    
         graph.getModel().endUpdate();
    
         // Creates an image than can be saved using ImageIO
         BufferedImage image = createBufferedImage(graph, null, 1, Color.WHITE,
                                                   true, null);
    
         // For the sake of this example we display the image in a window
         // Save as JPEG
         File file = new File(filename);
         ImageIO.write(image, "JPEG", file);
    
       }
      
      ...
        
    }

    第五步:生成可视化:

    图1中每个顶点的颜色代表证监会对该股票所属上市公司归类的板块

    可以看到,实际业务近似度较高的股票已经聚拢成簇状(例如:高速板块、银行版本、机场航空板块),但也会有部分关联性不明显的个股被聚类在一起,具体原因需要单独进行个股研究。

    JGraphT

    图1: 基于 2015-01-01 至 2020-01-01 的股票数据计算出的聚集性

    第六步:基于不同时间窗口的一些其他动态探索

    上节中,结论主要基于 2015-01-01 到 2020-01-01 的个股聚集性。这一节我们还做了一些其他的尝试:以 2 年为一个时间滑动窗口,分析方法不变,定性探索聚集群是否随着时间变化会发生改变

    JGraphT

    图2:基于 2014-01-01 至 2016-01-01 的股票数据计算出的聚集性

    JGraphT

    图3:基于 2015-01-01 至 2017-01-01 的股票数据计算出的聚集性

    JGraphT

    图4:基于 2016-01-01 至 2018-01-01 的股票数据计算出的聚集性

    JGraphT

    图5:基于 2017-01-01 至 2019-01-01 的股票数据计算出的聚集性

    JGraphT

    图6:基于 2018-01-01 至 2020-01-01 的股票数据计算出的聚集性

    粗略分析看,随着时间窗口变化,有些板块(高速、银行、机场航空、房产、能源)的板块内部个股聚集性一直保持比较好——这意味着随着时间变化,这个版块内各种一直保持比较高的相关性;但有些板块(制造)的聚集性会持续变化——意味着相关性一直在发生变化。

    Disclaim

    Disclaim

    本文不构成任何投资建议,且作者不持有本文中任一股票。

    受限于停牌、熔断、涨跌停、送转、并购、主营业务变更等情况,数据处理可能有错误,未做一一检查。

    受时间所限,本文只选用了 160 个个股样本过去 6 年的数据,只采用了最小扩张树一种办法来做聚类分类。未来可以使用更大的数据集(例如美股、衍生品、数字货币),尝试更多种图机器学习的办法。

    本文代码可见[18]

    Reference

    [1] 用 NetworkX + Gephi + Nebula Graph 分析<权力的游戏>人物关系(上篇)https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/

    [2] 用 NetworkX + Gephi + Nebula Graph 分析<权力的游戏>人物关系(下篇) https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph-part-two/

    [3] NetworkX: a Python package for the creation, manipulation, and study of the structure, dynamics, and functions of complex networks. https://networkx.github.io/

    [4] Nebula Graph: A powerfully distributed, scalable, lightning-fast graph database written in C++. https://nebula-graph.io/

    [5] JGraphT: a Java library of graph theory data structures and algorithms. https://jgrapht.org/

    [6] mxGraph: JavaScript diagramming library that enables interactive graph and charting applications. https://jgraph.github.io/mxgraph/

    [7] Bonanno, Giovanni & Lillo, Fabrizio & Mantegna, Rosario. (2000). High-frequency Cross-correlation in a Set of Stocks. arXiv.org, Quantitative Finance Papers. 1. 10.1080/713665554. 

    [8] Mantegna, R.N. Hierarchical structure in financial markets. Eur. Phys. J. B 11, 193–197 (1999).

    [9] https://graphviz.org/

    [10] https://gephi.org/

    [11] https://github.com/vesoft-inc/nebula-java

    [12] Nebula Graph Query Language (nGQL). https://docs.nebula-graph.io/manual-EN/1.overview/1.concepts/2.nGQL-overview/

    [13] Nebula Graph Query Engine. https://github.com/vesoft-inc/nebula-graph

    [14] Nebula-storage: A distributed consistent graph storage. https://github.com/vesoft-inc/nebula-storage

    [15] Neo4j. www.neo4j.com

    [16] JanusGraph. janusgraph.org

    [17] Apache Spark. spark.apache.org.

    [18] https://github.com/Judy1992/nebula_scan

    ]]>
    直播 | 阿里、携程、网易共同出品, 后端架构工程实践系列直播-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本次后端架构工程实践专场,由阿里技术、携程技术、网易技术共同出品。

    携程框架架构研发部负责携程后端框架和中间件的研发,包括异地多活、ServiceMesh、消息队列、配置中心等10多个产品,支撑携程的整体业务研发。

    阿里技术(阿里云开发者社区)提供分享、学习、认证、工具、资源、大赛、社群、MVP等一站式服务能力,满足开发者全生命周期成长需求。

    议题简介

    《携程DRC-MySQL数据双向复制实践》(戳我观看)

    直播时间:

    2020-09-21 19:00

    讲师介绍

    李明冬,携程软件技术专家, 围绕数据异地多活项目,负责DRC(数据实时双向或多向复制)项目设计与开发工作、 负责DAL(数据库访问中间件)项目设计和演进。

    内容简介

    DRC通过将部署在不同机房的MySQL数据进行双向复制,实现单机房保存全量数据,业务本地读写。单机房故障时,分钟级机房流量切换。本次演讲将深入剖析DRC内部设计和实现原理。你将了解到:
    1、DRC基本架构;
    2、DRC高可用设计方案;

    《携程第二代API网关设计与实践》(戳我观看)

    直播时间:

    2020-09-23 19:00

    讲师介绍

    俞炯,携程软件技术专家, 2016年加入携程,主要负责携程API网关的维护与研发,以及公共反爬相关基础设施建设。

    内容简介

    API网关是携程路由体系内的重要一环,在隔离&解耦网络环境、海外加速、单元化等场景内都承担着重要角色。网关同时也是公共业务需求的载体,包括安全认证、反爬、限流熔断、监控告警等。作为公共基础设施,保证自身稳定性,同时支撑业务发展是我们的首要目标。

    本次将着重分享内容包括:
    1、业务流量增长带来的性能问题;
    2、出海、上云、异地多活对架构的要求;
    3、多协议、多场景带来的治理问题;

    《Envoy 架构在网易轻舟的落地实践》(戳我观看)

    直播时间:

    2020-09-30 19:00

    讲师介绍

    王佰平,网易数帆轻舟事业部 工程师, 负责轻舟Envoy网关与轻舟Service Mesh数据面开发、功能增强、性能优化等工作。对于Envoy数据面开发、增强、落地具有较为丰富的经验。

    内容简介

    Envoy 是一款由 Lyft 开源的高性能数据和服务代理软件,CNCF 毕业项目。新兴 API 网关如 Gloo,Ambassador 都基于 Envoy 进行扩展开发;而在服务网格中,Istio、Kong 社区 Kuma、亚马逊 AMS App Mesh、微软 Open Service Mesh 都使用 Envoy 作为默认数据面。

    本次分享将详细介绍 Envoy 整体架构以及其功能特性,及经验总结:
    1、Envoy 是什么?
    2、Envoy 有什么?
    3、Envoy 能做什么?

    《云开发Serverless架构服务》(戳我观看)

    直播时间:

    2020-10-13 19:00

    讲师介绍

    杜欢(风驰),阿里云Serverless云开发平台负责人, 阿里巴巴前端委员会委员,致力于推动研发生态实现云端Serverless研发能力升级,实现云的开箱即用。 ​

    内容简介

    随着函数计算等 FaaS 云产品的不断完善,研发生态对 Serverless 的认知也日渐清晰,尝试也越来越多。在诸多实践中,许多人开始陷入一种迷思,FaaS 是不是只能用在一些 “计算任务” 场景?是不是只能在小程序这个场景才能用?K8S是不是很大规模的公司才能用?云原生,Serverless是不是只是说的好听,离我其实还很远呢?我们将为你揭示,一个真正人人可用的云时代研发模式已经到来!

    在此分享中,你会了解到阿里云Serverless云开发平台如何通过三套Serverless架构服务帮助应用落地到Serverless架构。包括:
    1、云原生时代的架构应该怎么做?
    2、Serverless架构有什么优势?
    3、应用如何落地?

    ]]>
    “云见教育 共享未来”阿里云让教育服务加速进步-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800     9月17日是2020年云栖大会拉开序幕的日子,今年的云栖大会以“数智未来·全速重构”为主题,进行了真正的“线上+线下”的深度尝试,除线上大会外,还在北京、上海、杭州、昆明、天津等城市开设了云栖大会阿里云MVP专场。其中,校宝在线独家承办“云见教育·共享未来”为主题的云栖大会的杭州分场,也是教育行业的专场。校宝在线作为中国深受欢迎的教育信息化综合服务提供商,一直以来以“推动教育服务加速进步”为使命,经过在教育SaaS领域的十年深耕,校宝在线已经帮助超90000个教育品牌实现信息化管理,帮助超过180000个校区实现“互联网+教育”的业务升级,服务教育从业者超100万。
        随着互联网技术的蓬勃发展,“互联网+教育”的转型升级已成为教育产业发展的重要议题,而云计算作为一门新兴技术正在改变各行各业的生产方式,其高可靠性、可扩展性和高度灵活性也正逐渐影响着教育行业的发展。2020年,教育行业正处于转型升级的关键时期,教育的形态焕新,秩序重组,如何借助互联网与云计算技术,实现教育行业的创新发展,成为本次云栖大会教育专场的重要议题。
    pt2020_09_21_13_45_42.jpg

        第一位嘉宾浙江大学教授、阿里巴巴-浙江大学前沿技术联合研究中心(AZFT)智能计算机系统实验室主任--陈文智,给大家分享了《教研云助力高效数字化转型》。陈教授展示了基于教研云的新一代教学平台,能够实现教学场景的全覆盖、AI优化教学质量、云化提升教学体验以及教学数据的多维分析,未来高校科研新模式将实现科研的数字化运营,打造科研协作空间,从而构建科研新生态。
        第二位嘉宾校宝在线CPO&创新事业部总经理--李杰,他是浙江大学管理学硕士、原阿里巴巴高级产品专家、原百度商业产品经理,这次给大家分享的主题是《透过疫情,看教培行业的DT未来》,现在IT时代正在逐步迈入DT时代,未来教育越来越多的基于数据驱动,因此教育行业也要顺势而为,拥抱DT,实现机构业务数据化、行业数据标准化、产业服务生态化的三步升级。
        第三位嘉宾阿里云智能解决方案架构师--胡中泉,作为十年阿里巴巴DBA,一直从事企业数字化转型数据库解决方案架构设计,这次带来的是《鱼熊兼得——云原生数据库技术解析》。云原生数据库存储计算分离,分别无限弹性,可具备HTAP特性,同时,云原生数据库关注弹性、性能和成本优化等上层需求,相比传统数据库,云原生数据库拥有更低的TCO、更好的性能与吞吐量,更便捷的使用体验更科学的使用方式。
        第四位嘉宾校宝在线架构师--李同刚,他是阿里云MVP、校宝在线共享技术部基建业务负责人,负责并参与了校宝云服务平台、DevOps平台、开放平台建设,这次分享了非常落地的《教育信息化平台DevOps实践》。DevOps 业务链路较长,因此我们要始终从业务问题出发,实现客户价值结合业务痛点,找到切入点,团队 Leader 要想好客户价值,聚焦、不要贪,“少则得,多则惑”,同时坚持大道至简,规范先行,从客户场景出发,方案做简单些,并且制定规范,做好期望管理。
    pt2020_09_21_14_01_58.jpg
        在四位精彩的嘉宾演讲之后,本次云栖大会教育专场还专门圆桌讨论了《云时代的教育SaaS平台技术演进之路》,校宝在线架构师肖伟宇、资深net开发工程师王斌与阿里云智能容量服务产品经理范捷惟、杭州互次方技术VP金立四位嘉宾在现场给大家带来了精彩有深入的交流探讨。
    pt2020_09_21_14_04_36.jpg

        每一位演讲嘉宾分享之后,现场的参会嘉宾也都提出了自己的疑惑和问题,现场交流气氛浓厚,大家对于新技术、新教育充满激情和能量。

    XO4A0510.JPG
    XO4A0503.JPG

    ]]>
    如何度量研发效能?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png
    没有可靠的度量就无法有效的改进,高度数字化的软件研发领域一直是进行各类效能度量尝试的创新重地。

    阿里云·云效服务的内部版本“Aone”承载着阿里集团数百个BU协同研发和持续交付的职责,笔者在数月前短暂的参与了该平台的效能透视镜板块建设,因而得以从平台的“上帝视角”重新审视效能度量这件事,随着项目开展,略微摸索了些门道。此文中观点源于这段时间里笔者在团队内以及与周边相关团队的讨论和个人思考,且作抛砖引玉之用。

    度量的分类

    度量的分类方式有很多,其中比较有意思的一种角度,是根据目标意图将度量划分为“针对人的度量”和“针对事的度量”。

    任何协作系统都离不开人的参与,加之可与绩效、考核等事情牵上关系,即使相关指标的分析往往伴随着争议,针对人的度量在企业里有时依然被视为一种“刚需”。譬如“代码量”、“代码质量”、“工作时长”等数据评判都是常见的依据指标。从产品实现而言,由于对结果可解释性要求高,这类度量的单因素指标居多,计算方案通常不会太复杂,宜采用小范围同维度横向比较,防止过度泛化。

    相比之下,针对事度量的范畴和方法更加灵活。既包括简单的数值指标,譬如产研中的发布频率、需求交付时长;也包括需要对比分析的多元指标,譬如需求在各阶段的停留时长、缺陷在各环境的漏测率等。在就事论事的基础上,为了更全面的理解事实的客观规律,还经常需要将一组数据向上聚合(譬如整个部门、整个项目的情况)或者跨领域关联(譬如业务领域需求关联到相关代码提交情况),从而获得更宽的观察视角。由于涉及的度量主体更多,有时为了确定哪个主体是主要的影响因素,还需要进行额外的归因判定。相较于以人为目标的度量,对事进行度量时,可以包含更多的经验和推理因素。

    对人或对事主要是针对度量目的而言,在实际运用时,两者采用的具体指标会有许多共同之处,并不能一概而论。根据管理学中的“平衡计分卡(The Balanced ScoreCard)”理论,度量活动要遵循“目标-度量-指标-行动”的规则,指标最终服务于目标的达成,好的度量产品不仅应当反映“发生了什么”,还应当能根据目标提供“该怎么做”的辅助建议。因此度量类产品的成败,不仅是对指标设计者的领域理解、抽象能力的挑战,而且对产品自身的业务目标清晰度也会提出很高的要求。

    效能的本质

    归根究底而言,效能的本质是对价值流动速度和质量的评价。

    “价值流”的概念伴随着精益思想的传播,被越来越多行业所接纳。不过很少有其他哪个行业能够像软件研发行业这样,能够让价值交付的各个环节几乎完全在线数字化,从而提供大量可分析的过程数据样本。

    所谓价值流动过程可以表示为,“价值原料”在可被度量的价值加工活动之间有序传递,不断叠加价值增量,最终形成可被消费的“价值产物”。下图将这一过程的度量抽象为一种非常简洁的表示结构,可称为效能度量的“元模型”。

    image.png

    度量中所用的各类“领域特征”则是由在此元模型之上的领域对象,以及基于这些对象的“领域指标”来定义的。

    譬如在研发领域,“价值原料”可以是一个业务方的需求,或是一个开发者突发奇想的创意。可被度量的活动包括需求拆解、任务指派、代码编写、测试、部署、验证、发布等等。每个活动本身都具有可被观测的属性,实体之间也具有可被量化的关系。这些实体、属性、关系就组成了特定领域的模型,下图展示了一种简化的研发度量领域模型(为了美观省略掉很多实体关系连接,仅作示意)。

    image.png

    有了领域模型,就可以基于规则制定指标。指标通常被描述为各种量化特征和实体属性的数值计算。有些指标是领域无关的,譬如端到端流通时长;有些指标是多个领域之间可以复用的,譬如许多行业都会有单位时间任务吞吐量、任务按时完成率这样的指标;有些指标是领域特有的,譬如研发领域的千行代码缺陷率等等。

    在指标之上,还需要有与具体运用场景相匹配的工具或平台来将度量结果转换为便于观察分析的表现形式。譬如各种图表、报表,以及事件通知。

    元模型和领域对象的分离,似乎能够形成一种足够抽象的通用度量产品,通过领域相关的指标规则、展示规则、通知告警规则,快速适配不同目标和场景,然而现实情况其实更复杂。一方面受制于计算能力,有些指标实际无法根据模型+规则实时计算出来,必须单独预先算好,以空间换时间。另一方面受限于价值增值过程的可观测性,并非所有行为的结果都能立即被简单量化(否则说服人们坚持锻炼身体就容易多了),即使在高度数字化的软件研发领域,依然存在数据质量和时效性问题,在使用数据时需要加以考虑。因此各种效能的场景虽然具有十分相似的流动特征,实际产品依然会不可避免的根据业务定制化,万能的度量工具或公式是不存在的。

    模型的存储

    对于度量模型的存储,图数据库可能是最好的选择,没有之一。

    相比结构化的SQL数据库和文档型的NoSQL数据库,图数据库属于比较小众的一种偏门奇术,主要用在知识图谱和基于关系的信息搜索领域。从基本特征而言,图数据库通常具备NoSQL的非结构化KV存储能力,允许同一类实体具有不同属性项的实例,这对于处理来自多种数据源或多个子类型的实体信息带来很大便利。同时,图数据库通常能像SQL数据库那样支持事务和多实体关联查询。不仅如此,图数据库对复杂关系的检索性能远高于SQL数据库,对于判断、循环查询的支持也比SQL存储过程更加优雅。

    然而这些基础能力上的差异,并非我推荐将图数据库用于效能度量的主要原因。

    好的技术选型应该能够充分适应潜在的业务需求变动,避免过早将技术实现耦合到局部的应用场景。在基于SQL表的开发模式里,“表结构设计”是在软件详细设计阶段里非常重要的一个环节,因为它不仅是对整体业务领域的建模,还关系着未来数据查询的效率和便利性。熟悉SQL表设计的同学应该知道,1对1、1对N、N对N关系,数据表的处理方法是完全不同的:N对N关系需要额外设计关联表,1对N关系通常是在后者的实体上设计外键,而1对1关系的外键设计就更有讲究了,要根据实际场景来决定该在哪个实体上放另一者的外键,然后在使用的时候顺着这个关联方向来查询。对于聚合的设计也是如此,需要事先在被聚合表上提前设计好用于聚合的外键,因此会有“事实表”、“维度表”的区分。数据的查询规则,在数据库表结构设定的时候就被确定下来了。

    对业务模式比较固定的场景而言,提前考虑好数据的使用方法并做针对性优化显得合情合理,然而效能度量业务并不属于此类。在度量领域里,关联、级联、聚合都是十分常见的指标计算操作,由于指标的作用在于发现潜藏于表面之下的问题,事先不应当提前规定只能从哪一类实体作为关联查询的起点,或者必须以哪些维度做聚合观察。

    就图数据库的存储模型来说,所有业务实体都是平等的,任何类型的关系都由实体间的关联来表示。这就像是在SQL表设计时,不论是1对1还是N对N关系,总是额外增加一张关联表,却无需顾虑多表JOIN带来的性能影响。这样一来,相当于将查询和聚合方式的决策推迟到实际使用的时候再做,从而有效解耦建模和查询时的相互制约,不再需要为优化查询而返工改表。

    此外,由于关联直接建立于实体之间,当删除实体的时候,实体间的关联也将自动断开。这就像有垃圾回收机制的Java语言不用自己管理内存指针一样。图数据库绝不会产生由于关系修改时的不对称清除而导致的数据不一致情况。

    那图数据库会不会有坑?肯定有。不过在我们目前有限的探索里,遇到比较大的麻烦主要来自它不够完善的周边工具配套、阿里云图数据库服务的某些配置限制,以及市场上稀缺具备相关技能的专业工程师。

    专家经验

    在研发效能领域,度量的终极目标是DevOps文化所提倡的识别和消除系统性瓶颈。

    通过各式各样的过程数据,经验丰富的项目经理和管理教练往往能够准确判断出项目的潜在问题和交付风险。

    在经济学领域有个十分有趣的“古德哈特定律”,即“当决策者试图以一个事物的客观测度指标作为指针来施行政策时,这一指标就再也不能有效测度事物了”。

    然而效能度量并不是玄学,价值生产活动中的风险应当是有章可循的。古德哈特式的此消彼长现象其实来源于经济领域的范围太过宽广,任何实用指标往往只能是局部度量的结果。效能透视镜产品的提出者嵩华老师曾经分享过一种识别研发项目系统性风险的思路,即有的放矢的关注四种典型的全局现象:

    • 流动阻滞
    • 返工
    • 落后的工程能力
    • 技术债务

    这几种现象不太容易在局部进行遮掩,且在一定条件下能够相互叠加,成为“烂项目”的标配。

    透过整个研发过程中的种种现象,找到反映这些全局性问题的蛛丝马迹,不仅能在一定程度上让“专家经验”产品化、标准化,也有助于将效能数据的使用方法从当前普遍的“事后复盘”式向以全局流动速率和质量作为关注点的“风险管控”式发展,从而在可靠性和时效性两个方面都得到提升。

    总结

    数据不会骗人,但数据的呈现和解读依然有很大的空间值得探索。现实事物复杂而多面,度量正是为描述和对比这些具象事实而采取的抽象和量化措施,从某种意义上来说,度量的结果一定是片面的,反映部分事实。没有银弹,也没有完美的效能度量。

    对于企业研发效能的提升,开发者工具、效能方法理论、效能度量指标都是缺一不可、环环相扣的几个重要板块,相信随着数据价值被越来越多的挖掘,我们终将实现更有效的反馈和更精确的赋能,让研发协作真正变得透明、简单、高效。

    最后

    分享十条前人总结的经验观点。

    • 任何指标一旦用于管控,就不再可靠(古德哈特定律)。
    • 测量的对象与人越近,越不可靠。
    • “凡可度量,皆可改造”是错的。
    • 变化趋势的价值高于指标绝对值。
    • 选择适当的而非“标准的”指标,若发现指标没用,果断舍弃。
    • 务必了解指标的获取成本,明确指标意图和针对的企业目标。
    • 设计“北极星指标”,指标数量越多,边际收益递减。
    • 不要将指标对所有人透明。
    • 让一线人员参与指标制定。
    • 如果可能,合理缩短度量周期。
    ]]>
    ARMS助力「掌游科技」系统重构-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 武汉掌游科技有限公司隶属于盛趣游戏(原盛大游戏)旗下,是一家经营手游发行、深度运营及综合买量的互联网游戏公司。自2014年成立至今,已成功运营超过1000款手游产品,拥有超过1亿游戏用户,活跃用户突破千万。

    系统重构后产生新的需求

    掌游科技有SaaS游戏发行运营平台,帮助游戏厂商通过大数据分析更好的运营游戏。随着业务量的不断上涨,之前单数据库的系统架构逐渐出现了性能瓶颈。

    为了能更好的支撑业务,掌游科技将单库按业务拆分为了四个数据库,整个系统也因为分库而进行了重构。但是重构后的系统经过测试发现有很多问题,几次上线后都出现了不稳定的情况,并且由于业务复杂,开发人员众多,很难快速、全面的发现系统Bug和性能约束点,所以急需通过APM工具来发现系统中存在的各个性能约束点。

    掌游科技期望APM工具能具备以下五点特性:

    • 快速无侵入的接入方式:期望最大限度的不改动系统代码,以无侵入、无感知的方式将监控系统接入现有系统;

    • 系统性能低损耗:接入的监控系统不对能宿主系统产生过高的性能损耗,期望不超过5%;

    • 可视化的配置操作方式:不期望繁琐的、通过命令形式的配置。期望可以通过Web控制台的方式方便的进行配置,比如采样率、SQL提取规则、业务监控配置等;

    • 全方位的应用监控能力:期望从应用层面、节点层面、接口层面、主机层面多维度监控系统。比如应用的整体健康度,接口的调用次数、响应时间、慢SQL分析、异常分析、JVM分析、内存快照分析、CPU/内存/IO分析等;

    • 支持PHP语言:客户主要使用PHP语言进行开发,希望监控系统可以全面支持PHP语言的各个组件和框架。比如CURL、PDO、Mysqli、Yar Client、GRPC Client、Predis Client、Memcache Extension等。

    解决方案

    阿里云自研的应用实时监控服务ARMS完美的契合了客户的诸多痛点,通过Agent的方式无侵入的、快速的接入客户系统,通过白屏化的方式进行设置,提供各种丰富完善的视图协助客户全方位监控系统,并且ARMS结合众多客户场景和专家经验,提供智能诊断功能。帮助客户快速、准确的发现和定位到了系统重构后的很多性能约束点和隐患,让客户有的放矢的对系统进行优化。

    1. 性能瓶颈和Bug一目了然:无论从CPU、内存、磁盘、网络、JVM GC、JVM堆内存、内存快照分析的主机层面还是从接口响应时间、请求数、错误数、异常分析、上下游接口、接口快照、调用链路、线程剖析的接口层面,亦或是慢SQL分析、数据库连接数、CPU/内存/磁盘使用率、网络流入/出流量监控的存储层面,以及NoSQL、MQ监控等。将系统360度无死角的监控起来,所有性能问题都无所遁形。

    2. 提前预知系统问题:ARMS提供了完善的预警通知功能,可以基于主机监控、JVM监控、异常接口调用、应用调用统计、数据库指标等多个指标类型下几十钟触发条件来灵活的设置预警规则。同时提供多样化的通知方式以满足不同客户的通知需求。

    3. 支持PHP:目前开源的APM工具对PHP支持的都不够完善,无论在无侵入接入方面,还是支持的组件方面,面对PHP语言构建的系统,都不能很好的支撑形成监控链路闭环。ARMS在对PHP语言系统的监控方面已经沉淀多年,绝大多数PHP的框架和组件都已支持,有很完善的问题分析定位的链路闭环和最佳实践。

    ]]>
    Spring 5 中文解析数据存储篇-DAO支持-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Spring核心篇章:

    Spring 5 中文解析之核心篇-IoC容器

    Spring 5 中文解析核心篇-IoC容器之依赖关系

    Spring 5 中文解析核心篇-IoC容器之Bean作用域

    Spring 5 中文解析核心篇-IoC容器之自定义Bean性质

    Spring 5 中文解析核心篇-IoC容器之BeanDefinition继承与容器拓展点

    Spring 5 中文解析核心篇-IoC容器之基于注解的容器配置

    Spring 5 中文解析核心篇-IoC容器之类路径扫描和组件管理

    Spring 5 中文解析核心篇-IoC容器之JSR330标准注解

    Spring 5 中文解析核心篇-IoC容器之基于Java容器配置

    Spring 5 中文解析核心篇-IoC容器之Environment抽象

    Spring 5 中文解析核心篇-IoC容器之ApplicationContext与BeanFactory

    Spring 5 中文解析核心篇-IoC容器之Resources

    Spring 5 中文解析核心篇-IoC容器之数据校验、数据绑定和类型转换

    Spring 5 中文解析核心篇-IoC容器之SpEL表达式

    Spring 5 中文解析核心篇-IoC容器之AOP编程(上)")

    Spring 5 中文解析核心篇-IoC容器之AOP编程(下)")

    Spring 5 中文解析核心篇-IoC容器之Spring AOP API

    Spring测试篇章:

    Spring 5 中文解析测试篇-Spring测试

    Spring 5 中文解析核心篇-集成测试之概要和集成测试注解

    Spring 5 中文解析核心篇-集成测试之TestContext(上)")

    Spring 5 中文解析核心篇-集成测试之TestContext(中)")

    Spring 5 中文解析测试篇-集成测试之TestContext(下)")

    Spring 5 中文解析测试篇-Spring MVC测试框架

    Spring 5 中文解析测试篇-WebTestClient

    Spring存储篇章:

    Spring 5 中文解析数据存储篇-Spring框架的事物支持模型的优势

    [Spring 5 中文解析数据存储篇-事务同步和声明式事物管理
    ](https://mp.weixin.qq.com/s?__biz=MzA3NDgzODYzNg==&tempkey=MTA3OV91TU8vcGlxSXdvTGNhZ2o0a3p2RXZvSGpJeXNCMmNCUkszbU9OZzVSc09rT19Zejl6b3JCWHZHU0JfN3ZrVDhhbzZUV3BfS2s3aHFEakhPb3V4dXVkMVp4ajFfZllOcnM2N3huU2d1ZUJZZlN6T1lZNVVKWHJjOWRkdEg3Uzg3RmpFRzZXbHMzQ3lFUUEwd1JqTl9JOGZzWGxMYWh6N1lhY05DYnFRfn4%3D&chksm=1f7b0caa280c85bcce8c4ffe9fb21629f683d5d9127116dae91dc9b9cbd2f367a19514fef76f#rd)

    [Spring 5 中文解析数据存储篇-@Transactional使用
    ](https://mp.weixin.qq.com/s?__biz=MzA3NDgzODYzNg==&tempkey=MTA3OV9RNU1VNnhsa0ZkRlhBb3Fla3p2RXZvSGpJeXNCMmNCUkszbU9OZzVSc09rT19Zejl6b3JCWHZHU0JfNTZ4dWhENjFrNUV6dlpieWYxVndQRlBNNkFRZVBFWlVyZHdiQlhTMmZMM0g0TnppT040Nk5QUU1rcEpNY2FDN09nZnNPeG5WTU8wajZCNUowaHZnTHhZcGpYdVRlNXQzWTZUSV8yOEpJNl9nfn4%3D&chksm=1f7b0cb3280c85a5682b1c9ea3cf7d2a69abdd6fe9147ed4335a5d5cfbef7c04ed314bd389de#rd)

    Spring 5 中文解析数据存储篇-编程式事物管理

    完整电子书地址

    Spring对数据访问对象(DAO)的支持旨在使以一致的方式轻松使用数据访问技术(例如JDBCHibernateJPA)。这使你可以轻松地在上述持久性技术之间进行切换,并且还使你无需担心捕获每种技术特有的异常即可进行编码。

    2.1 一致的异常层次结构

    Spring提供了从特定于技术的异常(例如SQLException)到其自己的异常类层次结构的便捷转换,该异常类层次结构以DataAccessException作为根异常。这些异常包装了原始异常,因此你永远不会丢失任何有关可能出错的信息。

    除了JDBC异常,Spring还可以包装JPAHibernate特定的异常,将它们转换为一组集中的运行时异常。这样,你就可以仅在适当的层中处理大多数不可恢复的持久性异常,而不必在DAO中使用烦人的样板捕获和抛出块以及异常声明。(尽管如此,你仍然可以在任何需要的地方捕获和处理异常。)如上所述,JDBC异常(包括特定于数据库的方言)也被转换为相同的层次结构,这意味着你可以在一致的编程模型中使用JDBC执行某些操作。

    在Spring对各种ORM框架的支持中,上述讨论对于各种模板类均适用。如果使用基于拦截器的类,应用程序必须关心处理HibernateExceptionsPersistenceExceptions本身,最好是通过分别委托给SessionFactoryUtilsconvertHibernateAccessException(..)convertJpaAccessException()方法。这些方法将异常转换为与org.springframework.dao异常层次结构中的异常兼容的异常。由于未选中PersistenceException,因此它们也可能被抛出(不过,在异常方面牺牲了通用的DAO抽象)。

    下图显示了Spring提供的异常层次结构。(请注意,图像中详细说明的类层次结构仅显示整个DataAccessException层次结构的子集。)

    DataAccessException

    2.2 用于配置DAO或存储类的注解

    确保你的数据访问对象(DAO)或存储库提供异常转换的最佳方法是使用@Repository注解。此注解还使组件扫描支持可以查找和配置DAO和存储库,而不必为其提供XML配置。以下示例显示了如何使用@Repository注解:

    @Repository //1
    public class SomeMovieFinder implements MovieFinder {
        // ...
    }
    1. @Repository注解

    任何DAO或存储库实现都需要访问持久性资源,具体取决于所使用的持久性技术。例如,基于JDBC的存储库需要访问JDBC数据源,而基于JPA的存储库需要访问EntityManager。完成此操作的最简单方法是使用@Autowired@Inject@Resource@PersistenceContext注解之一注入此资源依赖项。以下示例适用于JPA存储库:

    @Repository
    public class JpaMovieFinder implements MovieFinder {
    
        @PersistenceContext
        private EntityManager entityManager;
    
        // ...
    }

    如果使用经典的Hibernate API,则可以注入SessionFactory,如以下示例所示:

    @Repository
    public class HibernateMovieFinder implements MovieFinder {
    
        private SessionFactory sessionFactory;
    
        @Autowired
        public void setSessionFactory(SessionFactory sessionFactory) {
            this.sessionFactory = sessionFactory;
        }
    
        // ...
    }

    我们在此显示的最后一个示例是对典型JDBC支持的。你可以将DataSource注入初始化方法或构造函数中,在此方法中,你可以使用此DataSource创建JdbcTemplate和其他数据访问支持类(例如SimpleJdbcCall等)。以下示例自动装配数据源:

    @Repository
    public class JdbcMovieFinder implements MovieFinder {
    
        private JdbcTemplate jdbcTemplate;
    
        @Autowired
        public void init(DataSource dataSource) {
            this.jdbcTemplate = new JdbcTemplate(dataSource);
        }
    
        // ...
    }

    有关如何配置应用程序上下文以利用这些注解的详细信息,请参见每种持久性技术的特定介绍。

    作者

    个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!

    博客地址: http://youngitman.tech

    CSDN: https://blog.csdn.net/liyong1028826685

    微信公众号:

    技术交流群:

    ]]>
    【其他】9月21日阿里云域名交易平台未实名认证.cc/.tv域名下架通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【域名交易平台】【未实名.cc/.tv域名下架】

    维护时间:北京时间2020年9月21日 00:00

    维护内容:未完成实名认证的.cc/.tv域名将被系统下架一口价(万网)、域名竞价等出售信息。

    维护影响:自北京时间2020年8月10日 10:00起,.cc/.tv域名的注册、转入、持有者信息修改(过户)等操作将要求进行域名实名认证(包括命名审核和实名资料审核)。

    阿里云域名交易平台出售中的.cc/.tv域名若未完成实名认证,一口价(万网)、域名竞价等类型将无法被正常下单购买。请尽快完成.cc/.tv域名实名认证!

    出售中的.cc/.tv域名,若未能在9月21日00:00前完成域名实名认证,相关出售信息将被系统自动下架,您须待域名实名认证完成后,重新操作域名上架。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    关于2020国庆假期的安排-米姆-阿里云服务 Fri, 20 Jun 2025 02:20:33 +0800        根据国务院办公厅关于2020年部分节假日安排的通知,10月1日至8日国庆节,中秋节放假调休,共8天。9月27日(星期日)、10月10日(星期六)上班


           在此节假日期间亲爱的各位老铁,我们依然为您安排了值班工作人员哦!

           阿里云售前、售后:尹先生-13246463538

           阿里云技术支持:陈先生-14737363737

           其他服务需求与咨询:高先生-15869041323


         我们友好的小伙伴会一直陪在您身边,有问题速速联系我们吧!也可以移步至官网智慧表单提交您的需求或问题哦,我们会在第一时间处理。


         感谢大家在2020年对我们的支持,因为你们的信任,我们才能在激烈的市场竞争中不断进步与发展。未来我们不负年华,提升服务质量与体验。也祝您节日快乐。


                                                                                                             广州米姆信息科技有限公司

                                                                                                              2020年9月

    ]]>
    阿里云SAE支持「禾连健康」低门槛微服务化,弹性降本20%以上 Fri, 20 Jun 2025 02:20:33 +0800 166.jpg

    作者:黛忻

    浙江禾连网络科技有限公司(简称:禾连健康)成立于2014年,多年来专注医疗互联网产业,致力于通过互联网技术,建立服务患者、医生、医院的大平台。

    业务范围从医院Wi-Fi、患者端App、医护端App,拓展到提供一站式医院物联网应用解决方案。目前医院Wi-Fi已签约全网245个城市1700多家医院,成为中国最大的医院Wi-Fi服务商。禾健康、禾连保、禾医助几个App提供一站式家庭健康管理服务,服务1亿用户。

    客户需求:

    禾连是聚石塔的最早一批用户,采用ECS + Docker模式部署应用。因聚石塔定位的变化,没有持续维护相关业务;再加上禾连业务的不断发展,而聚石塔在弹性、服务治理上的能力不满足预期,禾连不得不考虑重新做技术选型。

    在架构优化初期,公司基于ECS自建整套微服务架构,过程中暴露了很多问题:

    (1)产品迭代跟不上业务变化:传统开发模式无法支撑公司业务的快速发展,研发既要忙于业务,又要Cover底层组件开发和运维,效率非常低;

    (2)硬件闲置成本高:云上按业务峰值保有大量的包年ECS,逐台购买安骑士服务。但业务具有潮汐特性,峰谷时闲置浪费高。而且业务瞬息万变,一旦发展不符合预期,购买的ECS会长期处于闲置状态。

    (3)系统维护成本高,无运维人员:公司无专职运维人员,系统相对复杂,微服务架构 + APM工具等均采用开源自研,开发&维护成本高。聚石塔上ECS + Docker高密部署的降本方案,集群初始化非常繁琐且耗时较长,需给每台ECS安装探针,运行一系列初始化脚本。研发精力严重分散,疲于奔命。

    (4)效率&稳定性:系统的扩缩容困难,流量高峰时效率得不到保证。且服务发布和重启的时候偶尔会影响线上业务稳定。

    使用Serverless技术构建新型互联网医疗应用

    基于Serverless应用引擎(SAE),提供了一个零改造、开箱即用的Serverless PaaS,最大程度帮助用户屏蔽底层IaaS、K8s运维、以及常用微服务组件的学习成本,解决了禾连长期以来运维成本高、开发迭代和弹性效率低,硬件闲置成本高等痛点问题。

    image.png

    降成本:节省自建微服务架构+APM的ECS成本,无需购买ECS安全骑士服务;基于秒级弹性能力,按需自动弹、同时通过一键启停开发测试环境,极大的提升了资源利用率,比ECS方案节省20%以上的硬件成本。

    提效率:提供了开箱即用的微服务治理、应用监控的能力,零运维零改造直接使用,支撑新业务快速上线;提升定位诊断效率,让企业专注于业务本身。

    极致弹性,发布无感知:基于SAE 的定时弹性和监控指标弹性(CPU、Memory等),无须容量规划,秒级弹性,便可轻松应对流量暴增,保障SLA。通过配置健康检查探针,系统自动检测,做到服务发布和扩缩容时业务无感知。

    一站式体验:自动集成阿里云基础设施类产品:SLB、SLS、NAS等和微服务生态产品:ACM、ARMS、AHAS,对接DevOps产品云效,提供一站式的上云体验。

    ]]>
    消息队列在线迁移实战 | 最佳实践 Fri, 20 Jun 2025 02:20:33 +0800 前言

    消息队列(Message Queue,下文简称MQ)是分布式互联网架构中必不可少的核心组件,包括RocketMQ、Kafka、RabbitMQ等在业界广泛使用的产品,在消息分发、异步解耦、削峰填谷、广播通知等领域发挥着巨大的作用。

    在MQ的使用过程中,在线对MQ组件进行迁移是一个非常普遍的需求,在如下的几个场景中,都会涉及到MQ的在线迁移:

    (1)规格升级。比如将3 Broker的Kafka集群替换成6 Broker的Kafka集群。

    (2)更换另一种MQ产品。比如将RabbitMQ替换为性能和扩展性更强的RocketMQ。

    (3)使用云服务替换自建MQ集群。比如将自建的RocketMQ集群替换为云上商业版RocketMQ服务。

    在MQ迁移的过程中,存在3个非常重要的需求:

    • 操作简单。
    • 风险可控。
    • 不影响业务系统的正常运行。

    如何让MQ的在线迁移同时满足这3个重要的需求呢?本文将对几种可行的方式进行深入探讨。

    理论基础

    在涉及到MQ在线迁移的所有方案中,都存有一个很重要的原则:对于发往MQ的每一条消息,如果已经被它的消费者成功接收并得到处理,这条消息就不再具有业务含义。已经被成功接收并得到处理的消息,只体现出统计方面的价值,并不需要随着MQ本身的迁移而进行数据迁移。

    在数据库迁移的场景中,新旧DB之间的数据迁移是非常重要的工作,这是因为DB中的数据是持久化的数据,需要伴随着数据库的生命周期而长期存在。

    而对于MQ而言,消息一旦被消息者接收并得到处理,就不再是持久化的数据,可以直接删除或归档。因此在MQ在线迁移的场景中,对已经处理过的消息,是没有数据迁移必要的。这样就将问题简化为:如何在迁移的过程中确保每一条消息被成功接收并得到处理。

    在系统维护期停业务迁移

    我们先从一个最简单方案开始体验MQ在线迁移是如何进行的。对于不要求7*24小时连续运行的业务系统,可以利用系统维护期的时间窗口,通过停业务的方式来实现消息队列的迁移。这种迁移方式因为不需要保证业务的连续性,操作起来就非常简单。

    (1)初始状态

    image.png

    进入维护窗口期后,关闭生产者应用,这个时候不会再有的新的消息写入MQ:

    image.png

    在这个状态下保持一段时间,当MQ上的所有消息都被消费者接收并成功处理后,就可以对消费者进行版本发布,使其从新的MQ上接收消息:

    image.png

    接下来再启动生产者,使其指向新的MQ,整个操作就已经完成。当系统运行稳定后,可以对原MQ实例进行相关数据归档后直接下线:

    image.png

    在停业务迁移方案中,最关键一步,在于如何在系统维护窗口期之内,确保原MQ上的所有消息被消费者接收并成功处理。因为生产者关闭之后,不会有新的消息写入原MQ,只要预留足够长的时间,原MQ上堆积的消息一定会被消费者取走。在这个方案中,新MQ和原MQ也可以是不同的产品,比如从RabbitMQ迁移到RocketMQ也是可以支持的,因为生产者和消费者都经过了版本发布的动作,只需要在新版本中对API和收发逻辑进行修改就可以实现。

    双订阅方案

    在互联网领域,能够容忍维护期将业务暂停的系统越来越少了,7*24不间断服务是行业的趋势,上述的停业务迁移方案就不再适用了,如何在MQ迁移的过程中确保业务持续运行呢?

    有一个非常棒的idea是让消费者同时具有从原MQ和新MQ接收消息的能力,这样不管生产方往哪一个MQ发送消息,都能够确保消息得到及时的处理。这是一种不需要暂停业务的方案,我们来看一下具体的步骤。

    首先对消费者进行改造,使其同时连上新老两个MQ,具备同时从新老MQ接收消息的能力,这就是所谓的“双订阅”:

    image.png

    接下来对生产者进行发布,使其往新MQ发送消息,等原MQ上堆积的所有消息被消费者接收并成功处理后,就可以对原MQ下线:

    image.png

    在对原MQ下线的时候,因为消费者还保持着双订阅的状态,所以最好先切断消费者与原MQ的连接,再关闭原MQ,否则会造成一些异常(取决于SDK的实现)。如果消费者的订阅逻辑实现的足够优雅,可以在不重启消费者的情况下,通过一个指令在线切断消费者与原MQ的连接。

    这个方案看似可以用一种对业务无损的方式在线迁移MQ,在实际操作中可行性却很低,其根本原因在于:同时从两个MQ接收消息的改造工作量极大。一般情况下,每一个消费者都在引入MQ产品对应的SDK,并通过MQ提供接入点与MQ建立连接后,接下来就只需要围绕业务逻辑完成所需要的消息订阅操作。这个时候要想同时从一个新的MQ接入消息,需要在代码层面对所有的订阅逻辑进行改造,这是一项非常复杂的工作。

    在新MQ和原MQ是不同消息队列产品的情况下,消费者需要同时引入两套不同的SDK,改造难度会变得更大。基于双订阅方案完成MQ的迁移后,还需要考虑将来清理掉消费者从原MQ接收处理消息的遗留代码,这也是需要一定工作量的。

    如果在MQ和消费者中间,能有一个中间件来实现双订阅的逻辑,是不是消费者的代码就不需要改造呢?答案是肯定的。但引入这样的中间件本身就是一项非常有挑战的工作,还增加了整个系统的复杂度,如果仅仅是为了MQ的在线迁移而引入一个新的组件,是得不偿失的。

    基于工作量和风险的考虑,尽量不要使用双订阅方案。

    最优方案

    双订阅的本质,在于存在一个消费者可以同时接收新旧两个MQ消息的中间状态,在这个状态下,不管生产者往哪个MQ发送消息,消息都可以得到及时的处理。能不能有一种更简单的方式让消费者可以同时接收新旧两个MQ的消息呢?当然有,而且实现方式更加的简单。

    在一个可靠的分布式微服务系统中,应用都可以通过增加节点的方式而进行水平扩容,为了确保整套系统的高可用性,每一个应用都不应该长期处于单实例运行状态,而是通过多个无状态的应用实例组成一个应用集群。因此,在真实环境下,不管是消息的生产者还是消费者,都至少有2个实例在运行。在迁移MQ的过程中,“消费者可以同时接收新旧两个MQ的消息”的中间状态,并不一定要让消费者的每一个实例都通过双订阅来实现,其实只要让一部分实例从原MQ接收消息,另一部分实例从新MQ接收消息就能满足了。通过这样的思路,能极大程度上简化MQ迁移的工作量,而且在迁移的过程中确保业务不受任何影响。

    在迁移之前,需要先把元数据信息从原MQ复制到新MQ集群,不同的消息队列产品之前元数据的格式不一样,需要根据业务场景进行元数据的转换,元数据包括Topic、Queue、消费组、基础配置等信息。

    image.png

    首先,通过灰度发布机制,让一部分消费实例连上新的MQ。如果之前的消费者是单实例,这个时候也可以增加一个新的消费实例来完成这个步骤:

    image.png

    接下来,让生产者往新的MQ发送消息,这个操作并不一定需要采取一刀切的方式,也可以通过灰度发布的方式让消息的转向逐步转移到新的MQ上来。最终,原MQ将不再接收新的消息,它上面堆积的消息总将会被成功接收处理,这个时候可以继续通过灰度发布的方式解除消费者与原MQ的连接,连接全部解除完之后,原MQ就可以关闭了。

    image.png

    在这个方案中,对于单个的生产者和消费者,都不存在同时连接新旧两个MQ的情况,因此在改造工作量非常小。而且迁移的过程通过灰度的方式实现,既不会影响业务,又可以进一步的降低风险,是消息队列在线迁移的通用方案。

    常见问题

    问: 为什么不在新旧MQ之间进行消息数据的同步?

    答:对于MQ而言,消息一旦被消息者接收并得到处理,就不再是持久化的数据,可以直接删除或归档。在迁移的过程中,新旧MQ之间消息数据同步是没有必要的,反而会增加迁移的难度,并导致消息被重复接收。

    问:迁移过程中需要验证消息幂等性吗?

    答: RocketMQ、Kafka等大多数消息队列产品都没有从消息队列服务端本身确保消息只投递一次,需要消费者自行实现对幂等性的保证。因此,不管在消息队列的迁移过程中,还是正常使用中,都应该借助数据库、Redis等外部系统确保消息的幂等性,否则会造成业务逻辑重复处理。

    ]]>
    构建在线教育弹性高可用视频处理架构实战 Fri, 20 Jun 2025 02:20:33 +0800 前言

    近些年,在线教育行业飞速发展,为整个社会的知识传播提供了前所未有的便利性。通过多种形式的在线教育平台,学员与教师即使相隔万里也可以开展教学活动。借助丰富的网络课件,学员还可以随时随地的进行学习,真正打破了时间和空间的限制。在各种形式的网络课件中,视频课件自然是最直观表现力最丰富的形式,因此视频课件的市场占有率也在逐年提升。

    视频处理需求分析

    对于在线教育领域的视频课件出品方而言,每天都要对大量视频内容进行处理,下图展示了一个比较典型的场景:

    image.png

    (1)用户上传一个视频到平台后,会先在对象存储中对视频源文件进行暂存。

    (2)平台对视频进行预处理,并打上水印。

    (3)平台将视频文件转换为其他格式,并对分辨率进行调整,以适配各种不同的终端设备的要求。

    (4)将处理好的视频文件保存回对象存储,并同步到CDN进行加速。

    虽然从流程上来讲,这个场景比较简单,但在技术上的挑战其实是非常大的。视频课件的原作者来自于在线教育平台的广大用户,可能是平台负责内容输出的内部用户,也有可能是签约的教师,或者是平台认证过的分享型用户。用户上传视频的操作并没有固定的频率,往往集中在几个时间段,存在明显的波峰波谷。在业务高峰期,视频处理的需求量非常大,有的在线教育企业每天要完成数万个视频的转码工作。对于负责建设视频处理系统的技术团队而言,这样的业务场景就留给了他们一系列的挑战:

    (1)如何确保这套系统在业务高峰期的高可用性?

    (2)如何让每一个上传的视频尽可能快的处理完?

    (3)如何尽可能的降低资源成本?

    (4)如何高效率的应对需求的频繁变更?

    基于这几个诉求,我们结合云计算的特点,来分析一下可行的解决方案。

    使用SaaS化的云服务完成视频处理

    随着各大云计算厂商产品线的不断丰富,我们可以很轻松的寻找到开箱即用的方案来解决这类典型的视频处理需求。以阿里云为例,视频点播类产品提供了视频采集、编辑、上传、媒体资源管理、转码处理、视频审核分析、分发加速于一体的一站式解决方案。

    877.jpg

    对于技术团队而言,采用这样的方案不用预先准备任何计算资源,甚至不用编写任何代码,就能够从无到有拥有一整套视频处理系统,完全不用考虑资源规划的问题。这样的方案非常适合在业务发展初级需要让系统快速上线的场景。

    但随着业务的不断发展,开箱即用的SaaS化方案还是存在不少的局限性,基于如下的原因,大多数的技术团队还是会选择自己建设视频处理系统:

    (1)对于之前已经通过FFmpeg技术实现的视频处理服务,因为涉及到复杂的业务逻辑,很难直接迁移到SaaS化方案上来。

    (2)高阶的视频处理需求必须使用代码来实现:比如音频降噪、插入动态Gif水印、按固定频率截帧等等。

    (3)使用高分辨率的大视频是行业趋势,对于超大视频的处理,比如10G以上的1080P视频,往往需要通过自定义的手段进行计算优化,才能保证处理的及时性。

    (4)在很多种场景下,自建视频处理系统都会带来明显的成本优势。

    (5)频繁的业务需求变更需要对整套系统进行更精细粒度的迭代管理,比如采用金丝雀策略降低新版本发布所带来的风险。

    那么如何建设一套同时具备高性能、高可用性、高灵活性、低成本特点的视频处理系统呢?

    基于分布式集群

    最典型的方案是申请一组云虚拟机,在每台虚拟机上部署视频处理应用,组建成一个可以水平伸缩的服务集服。当有新的上频上传的时候,可以触发一个处理任务,并通过负载均衡或消息队列对任务进行分发,接到任务的应用节点负责完成对应的任务。

    image.png

    通过这个架构,在业务高峰期,用户上传视频行为比较密集,可以增加服务集群的实例数量,来提升处理能力。在业务低峰期,可以减少服务集群的实例数量,来减少资源成本。

    此方案可以通过定制化的代码逻辑实现各种高阶的视频处理需求,灵活度非常高,配合可以水平伸缩的计算集群以及负载均衡机制,能同时满足性能和成本方面的需求,是一套被广泛采纳的方案。但在生产环境大规模运行的情况下,这套方案还是会暴露出很多问题:

    (1)维护工作量大。

    整套系统的维护工作量涵盖了虚拟机、网络、负载均衡组件、操作系统、应用等多个层面,需要投入大量的时间和精力来保障系统的高可用性与稳定性。举一个最简单的例子,当某个应用实例出现故障的时候,如何第一时间定位故障并尽可能迅速的将其从计算集群中摘除,摘除之后又如何保证之前没有完成的任务能够重新得到处理呢?这些都需要再配合完整的监控机制、故障隔离恢复机制来实现,甚至涉及到代码层的业务逻辑优化。

    (2)弹性伸缩能力滞后。

    有两种方式实现计算集群的弹性伸缩:通过定时任务触发,或者通过指标阈值(CPU利用率,内存使用率等)触发。不管采用哪种方式,都没有办法基于用户行为精细化管理,在遇到任务密度大幅度起伏的时候,会面临弹性伸缩能力滞后的问题。当来自用户的视频上传请求突增的时候,新增一个应用实例需要经过申请云资源>初始化>部署应用镜像>应用启动>加入负载均衡列表等多个阶段,即便通过Kubernetes+预留资源池等技术优化,也往往需要10分钟以上。

    (3)资源利用率低。

    滞后的弹性伸缩能力会导致伸缩策略制定的相对保守,造成计算资源的大量浪费,增加了使用成本,如下图所示:

    image.png

    有没有一种方案能能帮助技术团队专注于业务逻辑的实现,并可以根据用户的实际上传请求进行精细化的资源分配,实现资源利用最大化呢?随着云计算的飞速发展,各大云厂商都在积极探索新的方案,用更加“云原生”的方式来解决成本和效率的问题,阿里云提供的函数计算 + Serverless工作流就是这个领域非常具有代表性的方案。

    函数计算

    阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,开发者无需管理服务器等基础设施,只需编写代码并上传。函数计算会为自动准备好计算资源,以弹性、可靠的方式运行代码,并提供日志查询、性能监控、报警等功能,确保系统的稳定运行。

    相比传统的应用服务器保持运行状态并对外提供服务的方式,函数计算最大的区别是按需拉起计算资源对任务进行处理,在任务完成以后自动的回收计算资源,这是一种真正符合Serverless理念的方案,能最大化的提升资源利用率,减少系统系统维护工作量和使用成本。因为不需要预先申请计算资源,使用者完全不需要考虑容量评估和弹性伸缩的问题,只需要根据资源的实际使用量来进行付费。

    下图展示了函数计算的工作方式:

    image.png

    对于使用者而言,把实现关键业务逻辑的代码上传到函数计算平台,就能以事件驱动的方式触发函数执行。函数计算已经支持各种主流的编程语言,对于即有的代码,可以通过几个非常简单的步骤部署到函数计算。函数支持的所有开发语言请参考开发语言列表。

    每一次计算资源的分配,都基于事件的触发,一个事件往往对应着业务上的一个任务。函数计算支持多种多样的触发器,比如HTTP触发器的事件源就是HTTP请求,函数计算接收到一次HTTP请求后,会按照预设的规格,分配相应的计算资源来处理这个HTTP请求,请求处理完成之后,函数计算会根据用户的设置决定是否立即回收这一次拉起的计算资源。而OSS触发器,能够监控发生在对象存储OSS上的各种事件,当有用户上传新文件或者对文件进行修改的时候,自动触发函数执行,这种方式就刚好适合视频处理的业务场景。更多支持的函数触发器请参考触发器列表。

    在计算资源的调度上,函数计算进行了大量优化,面对用户请求的突增,可以在毫秒级拉起大量的计算资源来并行工作,确保用户体验。

    通过函数计算进行视频处理

    基于函数计算的特性,搭建一套视频处理系统就非常简单,只需要配置一个OSS触发器,并将视频处理的核心代码上传到函数计算,就大功告成:

    image.png

    通过这套方案,使用者不再需要考虑资源管理、负载均衡、系统高可用、弹性伸缩、系统监控等一系列复杂的问题,函数计算平台会按最优的方式根据用户的上传行为调度计算资源,低成本高效率的完成视频处理任务。具体的操作步骤和代码实现可以参考视频处理Python实现Demo,在这个Demo中,演示了如何基于函数计算将用户上传的视频统一转为640 * 480分辨率的mp4格式视频。

    代码开发

    每一个创建好的函数都会对应一个指定的入口,函数计算会从这个函数入口开始执行,类似于本地开发中的Main()函数。以Python语言为列,一个简单的入口函数如下:

    def handler(event, context):
        return 'hello world'

    当有事件触发的时候,就会从入口函数开始执行,其中event参数携带了事件源相关的信息,比如在视频处理场景中,event参数携带了上传到OSS的Bucket以及文件名等信息。而context参数携带了函数的运行信息,包括函数名、超时时间、访问凭证等。通过这些信息,就能让执行代码完成预定义的各种操作。

    函数计算支持各种主流的编程语言,在这个编程语言当中,Node.js和Python等脚本型语言含了丰富的类库,开发效率很高,而且运算实例启动的速度很快,能够支持对延迟特别敏感的任务,是函数计算最匹配的语言。Java和Go等语言不能像脚本型语言一样直接上传代码就能创建一个函数,需要预先进行编译,使用起来会稍微复杂一些,但配合函数计算提供的Funcraft等工具,也可以大幅度提升开发和部署的效率。不管使用哪种开发语言,都建议使用者下载官方提供的Funcraft工具,更轻松进行开发、构建、部署操作,请参考Funcraft。

    像Java这样的语言,在虚拟机启动的时候需要加载比较多的类库,不能够像实现运算实例毫秒级启动并进入执行状态,不能直接使用在一些对于延迟特别敏感的业务场景。但配合函数计算提供的预留实例以及单实例多并发新功能,能够消除冷启动对业务的影响,并降低等待下游服务响应的影响,让函数计算上运行的Java语言也能实现API网关等对延时要求特别高业务场景。请参考预留实例和单实例多并发。

    Serverless工作流

    通过前面介绍的方案,可以轻松完成对短视频的各种定制化处理。但每一个函数计算实例,在资源规格上和总运行时长都不是无限的,目前函数计算实例可以拥有3G的内存资源和10分钟的执行时间,这也就说明,当一个视频处理任务需要占用3G以上的系统内存,或者总执行时长超过10分钟的情况下,处理任务是会失败的。

    在5G时代,超大视频课件是非常普遍的需求,如何通过函数计算处理这样的大视频呢?这个时候就要出动另一个武器---Serverless工作流,来配合函数计算一起完成这个任务。

    Serverless 工作流是一个用来协调多个分布式任务执行的全托管云服务。您可以用顺序、选择、并行等方式来编排分布式任务,Serverless 工作流会按照设定好的步骤可靠地协调任务执行,跟踪每个步骤的状态转换,并在必要时执行用户定义的重试逻辑,以确保工作流顺利完成。Serverless 工作流通过提供日志记录和审计来监视工作流的执行,方便您轻松地诊断和调试应用。

    image.png

    您可以使用 Serverless 工作流编排一系列的函数资源,同时定义流程中每一步的输入和输出,使用内置控制步骤编排复杂逻辑、发起并行执行、管理超时或终止流程。另外通过控制台能够使用图形界面显示出执行任务状态和执行顺序,同时控制台会显示每个步骤的实时状态,并提供每次执行的详细历史记录。通过Serverless工作流 + 函数计算的组合,我们可以突破时间和空间的限制,对任意大小的视频文件进行复杂的处理。

    大视频处理

    简单来讲,处理一个大视频的基本思路是:

    (1)将视频先进行切片处理,把每一个分片的大小控制在合理的大小,以便单个函数计算实例可以对其进行快速处理。

    (2)拉起多个函数计算实例对每一个分片进行并行处理。

    (3)对处理结果进行合并。

    通过Serverless工作流 + 函数计算进行视频处理的流程如下:

    image.png

    通过Serverless工作流提供的可视界面,我们能在工作流执行的过程当中,方便的查看到每一个步骤运行的信息,并配合自定义的Dashboard实现对整套视频处理系统的全面监控:

    image.png

    方案对比22.png

    总结

    基于函数计算和Serverless工作流的弹性高可用视频处理架构,充分体现了云原生时代Serverless化思想,以事件驱动的形式触发函数执行,真实计算资源真正意义上的按需使用。

    对于使用而言,这套方案在保证业务灵活度的同时,可以显著降低维护成本与资源成本,并大幅度的缩短项目交付时间。

    在线教育领域对于视频处理的需求量非常大,而且对于处理速度、并发吞吐量、资源利用率等方面都有极高的要求,函数计算 + Serverless工作流方案组合帮助用户轻松建设弹性高可用的视频处理架构,是实现这些复杂需求的最优解。随着云原生的不断发展,Serverless相关技术还将深入更多的业务场景,有未来有无限可能!

    ]]>
    阿里云资深技术专家姬风:Serverless 引领云的下一个十年 Fri, 20 Jun 2025 02:20:33 +0800 十年前通过推出云服务器,云计算拿下了第一桶金。这种基于服务器的云服务,帮助客户节省了对 IDC 的机器采购和运维成本,同时也保持了传统服务器运维的习惯。但服务器里面运行的操作系统、应用软件,以及整个分布式架构的运维复杂度,依然没法得到彻底解决,企业为此也投入了大量成本。

    事实上,基于服务器的云服务并不是云时代的终态。

    试想一下,如果服务器的概念被进一步抽象,那么与服务器有关的维护工作都会变成由云来承担。这就是我们常说的Serverless。过去十年,云正在逐步向 Serverless 演进。阿里云最初发布的 ECS 是服务器抽象,随着云原生技术的发展,Docker 容器让应用运行变得简单,Kubernetes 让集群运维变得简单。

    2016 年,阿里云发布的函数计算提供了函数级抽象,2019 年发布的 SAE 提供了应用级抽象,这些产品都抹去了服务器的概念,让用云方式得到极大的简化,并逐渐成为趋势。Serverless 已经不仅仅只有函数一种形态,它应该有不同的抽象级别。

    阿里云有4种不同的 Serverless 产品,分别提供了容器实例、容器编排、应用、函数的抽象。抽象级别低的产品,客户会拥有更大的管理灵活度;抽象级别高的产品,由云承担的工作会越多,客户的研发和运维的效率也会越高。

    这些 Serverless 产品可以给客户、给开发者带来什么样的价值呢?

    Serverless有三大核心价值:

    一是快速的交付:Serverless 通过进行大量的端对端整合以及云服务之间的集成,为应用开发提供了最大化的便利性,让开发者无需关注底层的 IaaS 资源,而更专注于业务逻辑开发,聚焦于业务创新,大大缩短业务的上市时间。

    二是极致的弹性:在 Serverless 之前,一旦遇到突发流量,可能会直接导致各种超时异常,甚至是系统崩溃的问题。即使有限流保护以及提前扩容等手段,依然会出现评估不准的情况,进而引发灾难性的后果。有了 Serverless 之后,由于具备毫秒级的弹性能力,应对突发流量会变得更加从容。

    三是更低的成本:就跟生活中用水电煤一样,我们只为实际消耗的资源买单,而无需为闲置的资源付费。Serverless 提供的端到端的整合能力,极大地降低运维的成本与压力,使 NoOps 成为可能。

    基于快速交付、智能弹性、更低成本的三大核心价值,Serverless 被认为是云时代的全新计算范式,引领云在下一个十年乘风破浪。 那么下一个十年的 Serverless 将会有什么趋势呢?

    第一,标准开放。通过支持开源的工具链和研发框架,Serverless 能够在多云环境下使用,无厂商锁定,免除客户后顾之忧。

    第二,云原生结合。与业界认为容器和 Serverless 有对立关系不同,阿里云 Serverless 将借助容器出色的可移植性和灵活性,实现应用交付模式统一;通过复用云原生生态,Serverless 在存储、网络、安全、可观测等方面更加标准、强大。

    第三,事件驱动。通过采用统一的事件标准,如 CloudEvent来建立云上的事件枢纽,让 Serverless 开发集成云服务、云边端应用更简单。

    第四,解锁更多业务类型。Serverless 早已不再局限在代码片段、短任务、简单逻辑,长时间运行、大内存的任务,有状态的应用,以及 GPU/TPU 的异构计算任务都会在 Serverless 产品上得到支持。

    第五,更低成本。在使用成本方面,采用 Serverless 产品的 TCO 会比基于服务器自建更低,一方面是引入预付费等计费模式,比按量节省 30% 以上;另一方面,随着 Serverless 不断演进,更大的资源池、更高的资源利用率,成本会进一步压低。在迁移成本方面,可以通过选择不同形态的 Serverless 产品,采用迁移工具,甚至一行代码不改,存量应用就能迁上 Serverless,享受 Serverless 红利。

    阿里巴巴的 Serverless 实践在业内处于领先地位,不仅淘宝、支付宝、钉钉、闲鱼等已经将 Serverless 应用于生产,Serverless 产品更为新浪微博、石墨文档、跟谁学、Timing 等各行各业的企业提供服务,助力企业快速数字化。而能够做到这些,离不开阿里云在 Serverless 战略上的坚持与技术投入。

    Serverless如何开启云的下一个十年,闲鱼是如何布局Serverless?一个没有服务器的世界是什么样子的?所有关于Serverless的热点话题,我们将在9月18日13:00 云栖大会「Serverless,引领云的下一个十年」全面揭秘!

    serverless海报.png

    扫码或点击预约直播,获取Serverless 实践与趋势解读。

    ]]>
    2020年容器应用的新思考 Fri, 20 Jun 2025 02:20:33 +0800 阿里云容器服务自从2016年5月正式推出,已经走过4年的旅程,服务了全球上万家企业客户。过去几年,阿里云和业界各位共同见证了容器技术的快速发展。

    容器镜像已经成为了分布式应用交付的标准,Kubernetes已经成为了分布式资源调度的标准:向下封装资源,向上支撑应用,已经成为了云应用操作系统,帮助应用以一致的方式运行在公共云、边缘计算和专有云环境中。在Kubernetes之上,也浮现出服务网格等新技术,进一步简化分布式应用架构开发和运维。

    阿里云提供了经典的云原生容器产品,包括:

    • Kubernetes服务 ACK,提供云端最佳容器运行环境
    • 镜像服务 ACR,管理各种云原生应用资产
    • 托管服务网格 ASM,加速新一代云原生应用架构落地

    容器已经无处不在了。越来越多的应用,通过容器方式进行管理、交付,从Web应用、数据库、消息等中间件,再到数据化、智能化应用。

    基于容器技术构建的新架构,会催生新的应用业务价值。其中,云原生 AI 是非常重要的应用场景,快速搭建 AI 环境,高效利用底层资源,无缝配合深度学习的全生命周期。好未来的AI中台基于容器服务搭建, 不仅帮助AI服务应对各种资源的急剧增长,而且解决了5个9的SLA需求。好未来AI中台技术负责人刘东东将会在18日的分享中详细讲述更多技术落地细节。

    作为容器服务的提供者,容器技术会继续发展,被应用于“新的计算形态”、“新的应用负载”和“新的物理边界”。云原生技术理念使企业用户及开发者只需要关注应用开发,无需关注基础设施及基础服务。

    因此除了上述的三款经典产品,阿里云还推出了 Serverless容器服务(ASK)和边缘容器服务(ACK@Edge)两款产品。

    Serverless容器服务(ASK)遵循Serverless 理念,将应用服务资源化并以 API 接口的方式提供出来,使用者只需从客户端发起调用请求即可,更重要的是,pay as you go 能够真正为用户节省成本。18日的分论坛演讲中,微博架构师胡春林先生将分享微博如何基于Serverless容器应对突发热点。

    此外,容器最被熟知的基础环境是数据中心,在业务流量高峰与低谷之时,凭借容器极致弹性可以实现应用与资源伸缩,有效地保证高利用率与高性价比。随着 5G 和物联网时代的到来,传统云计算中心集中存储、计算的模式已经无法满足终端设备对于时效、容量、算力的需求。将云计算的能力下沉到边缘侧、设备侧,并通过中心进行统一交付、运维、管控,将是云计算的重要发展趋势。以 Kubernetes 为基础的云原生技术,在任何基础设施上提供与云一致的功能和体验,实现云 - 边 - 端一体化的应用分发, 支持不同系统架构和网络状况下,应用的分发和生命周期管理,并且针对边缘及设备进行如访问协议、同步机制、安全机制的种种优化。

    容器可以适用于多种基础环境,比如数据中心、边缘云、和多云 / 混合云,使得开发者关注回归到应用本身。今年,阿里云容器服务也将迎来一系列新产品特性,并在9月18日「企业云原生创新与实践」分论坛上重磅发布。我们提前揭秘其中一部分新产品。

    第一个是容器服务ACK Pro版。它是针对金融、大型互联网、政企客户对安全、稳定、规模的更高需求,推出的高度优化的K8s运行环境。它具备几个关键优势:

    (1)基于神龙架构,软硬一体化优化设计,提供卓越性能;
    (2)提供高效调度能力,可以有效优化计算成本;
    (3)为企业提供全面安全防护,国内首批通过可信云容器安全先进性认证。

    ACK Pro正式商用后将提供可赔付的SLA。

    第二个是基于ACK的云原生AI加速器。Gartner预测到2023年,70% AI应用构建在容器和Serverless环境中。在过往的几年,阿里云帮助众多企业和阿里云计算平台等内部团队基于云原生技术构建AI平台。通过容器服务Kubernetes,可以对多种异构计算资源进行统一管理,提升资源利用率,降低计算成本。通过GPU共享等技术可以实现资源利用率2~4倍提升。

    通过K8s调度优化和分布式缓存等技术,可以提升AI计算效率。在模型训练场景,计算效率提升20%。此外,通过对AI任务提供统一流程管理,构建可重现、可移植、可组装的的AI流水线,AI工程效率可以提升50%。在此基础上,容器服务ACK推出云原生AI加速器,将把异构计算资源管理、调度优化,AI任务流水线,和上层AI算法引擎等全栈优化能力有机整合在一起。 它面向企业AI平台建设者,可以让客户自由组合各层能力,构建符合自己需求的AI平台, 加速算力的释放。

    以好未来为例,它是K12在线学习的领头羊,基于ACK的云原生AI加速器,提供了上百个AI服务。峰值请求量达每天8000万次。通过GPU共享调度,节省了近50%的资源成本。

    第三个是容器镜像服务企业版 ACR EE。它为众多跨国企业、互联网、在线教育、游戏等公司提供企业级云原生应用管理和分发能力。ACR EE支持容器镜像和所有符合OCI标准的应用制品管理,比如Helm Chart。以欢聚时代为例,它作为互联网出海的代表企业,其容器化应用需要在全球多地域、多 IDC 部署。使用 ACR EE,全球应用分发效率提高 85% ,成本仅为自建 1/3。

    第四个是阿里云托管服务网格ASM。Gartner预测,81%的企业将采用多云/混合云战略,混合云架构已经成为企业上云的新常态。在云原生时代,以Kubernetes为代表的技术屏蔽了基础设施的差异性,可以帮助企业在混合云环境下,进行统一资源调度和应用生命周期管理。

    阿里云托管服务网格ASM,是业内首个全托管,与Istio全兼容的服务网格产品。 服务网格的控制平面的组件托管在阿里云侧,用户集群只包含数据平面组件。这个架构解耦了服务网格与K8s集群的生命周期,简化升级和问题诊断;提升了系统的可伸缩性和稳定性。此外ASM全面整合阿里云可观测性、安全等能力,大大简化了服务网格生产环境的运维管理。

    9月,ASM正式商用,阿里云容器服务负责人易立将在9月18日云栖大会「企业云原生创新与实践」分论坛详细介绍ASM的新特性。

    去年阿里云发布边缘容器服务ACK@Edge。帮助企业将云计算能力延展到边缘,降低应用网络延时,简化海量设备的分布式管理。短短一年,已经广泛应用于音视频直播、云游戏、工业互联网、交通、物流、能源、城市大脑等应用场景中。

    这一次,ACK@Edge 与阿里云智能接入网关服务 SAG 共同推出多链路、云边协同的网络方案,具备安全加密、就近接入、即插即用等多种能力,使能边缘资源、业务一站式接入上云。在边缘计算领域,阿里巴巴云原生边缘计算平台 OpenYurt 在9月9日正式成为 CNCF 沙箱级别项目(Sandbox Level Project),标志着 OpenYurt 在边缘计算场景中构建云原生基础设施的能力受到了行业的广泛认可。

    此外,在本次云栖大会上,阿里云原生还将发布沙箱容器 2.0等重磅产品,通过核心技术解读与案例分享,帮助企业获得更加敏捷、高效的容器实现。

    更多精彩内容,敬请期待9月18日13:00 「企业云原生创新与实践」分论坛。

    18 企业云原生-商圭.png

    预约直播:https://yunqi.aliyun.com/2020/session88

    ]]>
    首届云原生编程挑战赛精彩盘点:他们如何从10000多支团队中脱颖而出? Fri, 20 Jun 2025 02:20:33 +0800

    云原生编程挑战赛,是“中间件性能挑战赛”的升级,赛题升级、赛道升级、报名团队数超过10000支,创下同类大赛报名新高。

    9月14日,阿里云原生编程挑战赛决赛答辩线上直播完美落幕,10支进入决赛的团队用精彩的答辩,为历时4个月的大赛画下了圆满的句号。其中,greydog团队以出色的方案、创新的优化思路、过硬的技术实力斩获冠军。

    大赛专家评审团认为,greydog团队的方案思路清晰,提出的7个技术优化点具有可落地性,在冷启动处理、OOM处理和大压力请求或超多函数场景下的解决思路具有很高的借鉴意义,能深入到赛题核心,勇于创新,具有较高的技术价值和应用价值。

    冠军.jpg

    ONE PIECE团队、睡衣小英雄团队并列亚军,四字成语团队、afkbrb团队、穿山甲团队获得季军。另外四支团队:井底虾蟆笔尖蛙Errr、hello,靓仔、亚洲王子王云轩、hehehlin获得优秀奖。

    阿里云研究员、本次云原生编程挑战赛的决赛评委之一的叔同,在决赛答辩结束后,跟在场的选手们分享了他对于云原生的理解。

    “过去我们常以虚拟化作为云平台和与客户交互的界面,为企业带来灵活性的同时也带来一定的管理复杂度;容器的出现,在虚拟化的基础上向上封装了一层,逐步成为云平台和与客户交互的新界面之一,应用的构建、分发和交付得以在这个层面上实现标准化,大幅降低了企业 IT 实施和运维成本,提升了业务创新的效率。

    从技术发展的维度看,开源让云计算变得越来越标准化,容器已经成为应用分发和交付的标准,可以将应用与底层运行环境解耦;Kubernetes 成为资源调度和编排的标准,屏蔽了底层架构的差异性,帮助应用平滑运行在不同的基础设施上;在此基础上建立的上层应用抽象如微服务和服务网格,逐步形成应用架构现代化演进的标准,开发者只需要关注自身的业务逻辑,无需关注底层实现,云原生正在通过方法论、工具集和理念重塑整个软件技术栈和生命周期。

    以容器为代表的云原生技术,用开放、标准的技术体系,帮助企业和开发者在云上构建和运行可弹性扩展、容错性好、易于管理、便于观察的系统,已经成为释放云价值的最短路径。最早创造和应用容器技术的是互联网公司,今天有了开放标准的云原生生态,使得容器技术得到迅速普及,越来越多的企业和开发者使用容器构建应用,共同享受这一技术红利。”

    一、为什么决赛选择了Serverless这一技术方向?

    十年前通过推出云服务器,云计算拿下了第一桶金。这种基于服务器的云服务,帮助客户节省了对 IDC 的机器采购和运维成本,同时也保持了传统服务器运维的习惯。但服务器里面运行的操作系统、应用软件,以及整个分布式架构的运维复杂度,依然没法得到彻底解决,企业为此也投入了大量成本。

    事实上,基于服务器的云服务并不是云时代的终态。
    试想一下,如果服务器的概念被进一步抽象,那么与服务器有关的维护工作都会变成由云来承担。这就是我们常说的Serverless。过去十年,云正在逐步向 Serverless 演进。阿里云最初发布的 ECS 是服务器抽象,随着云原生技术的发展,Docker 容器让应用运行变得简单,Kubernetes 让集群运维变得简单。

    阿里云Serverless负责人、本次云原生编程挑战赛决赛评委之一的不瞋在分享中提到,2016 年,阿里云发布的函数计算提供了函数级抽象,2019 年发布的 SAE 提供了应用级抽象,这些产品都抹去了服务器的概念,让用云方式得到极大的简化,并逐渐成为趋势。Serverless 已经不仅仅只有函数一种形态,它应该有不同的抽象级别。

    总的来看,Serverless有三大核心价值:

    一是快速的交付:Serverless 通过进行大量的端对端整合以及云服务之间的集成,为应用开发提供了最大化的便利性,让开发者无需关注底层的 IaaS 资源,而更专注于业务逻辑开发,聚焦于业务创新,大大缩短业务的上市时间。

    二是极致的弹性:在 Serverless 之前,一旦遇到突发流量,可能会直接导致各种超时异常,甚至是系统崩溃的问题。即使有限流保护以及提前扩容等手段,依然会出现评估不准的情况,进而引发灾难性的后果。有了 Serverless 之后,由于具备毫秒级的弹性能力,应对突发流量会变得更加从容。

    三是更低的成本:就跟生活中用水电煤一样,我们只为实际消耗的资源买单,而无需为闲置的资源付费。Serverless 提供的端到端的整合能力,极大地降低运维的成本与压力,使 NoOps 成为可能。

    基于快速交付、智能弹性、更低成本的三大核心价值,Serverless 被认为是云时代的全新计算范式,引领云在下一个十年乘风破浪。

    二、他们凭什么赢得阿里云众多技术专家的认可?

    今年是云原生编程挑战赛的第一年,是过去5年“中间件性能挑战赛”的升级。大赛一经发布,就吸引了众多开发者和高校学生的注意力。据统计,本次云原生编程挑战赛总计报名团队11060支,初赛分为三大并行赛道:【赛道1】实现一个分布式统计和过滤的链路追踪;【赛道2】实现规模化容器静态布局和动态迁移;【赛道3】服务网格控制面分治体系构建。

    通过三大赛道,为参赛团队更多的选择,从而让更多的开发者能够加入到比赛中来。

    正如云原生编程挑战赛负责人天北所说,“为了让这么庞大的参赛团队有充足的时间备战,同时也给评委更多的时间从这么多优秀的团队和作品中做出合理公正的选择,我们将大赛的赛程设置为4个月的长期赛,对于每个坚持到最后的参赛团队而言,都是一场脑力与耐力的比拼。在大赛结束之后,我们也会把优秀的作品分享出来,让大家了解这次大赛的技术方案和参赛团队的一些创新的想法,从而赋能更多的开发者。”

    本次大赛与其他编程类大赛最大的不同在于,云原生是未来十年最热的技术趋势之一,对于每一个参赛团队而言,所要处理的技术问题和挑战,都是未来大家真正深入到工作中会用到的方法和技能。每个赛题都来自于阿里云技术专家多年的实践思考,从技术痛点出发,真正能帮助选手了解Serverless、Service Mesh、Kubernetes等当下热门技术的核心。在这段比赛旅程中,阿里云的技术专家全程在钉钉群中为选手答疑解惑,启发大家从技术本身出发了解云原生是什么,解决什么问题,未来还有哪些想象力。

    能从10000多支团队中脱颖而出,这十支战队有何“秘密武器”?

    在赛后对这些参赛团队采访时,我们发现,他们对于新技术和新趋势抱有极大的学习热情,对于不懂的知识点会抽出时间来学习,对于已完成的方案仍然十分挑剔,每一处优化都希望尽可能达成更极致的效果。经历了4个月的磨砺,对于坚持到最后的这10支队伍而言,不仅结交到了一群热爱技术的朋友,更拓展了视野,接触到了最前沿的技术趋势。

    而这4个月,对于选手的考察不仅是脑力的比拼,更是耐力的考验。在答辩现场,面对众多阿里云技术专家的“灵魂拷问”,这些选手们能够从容不迫地阐述方案、讲解技术优化思路,并在与专家们的交流中收获经验。

    900.png

    三、云原生在未来十年将进一步释放技术红利

    云的技术发展会领先于企业落地的速度。尽管云原生技术已经被广泛接受,其在企业技术栈的落地仍然需要时间,也面临不少挑战。如容器化过程中改变传统虚拟机模式下的运维习惯,企业传统应用分布式微服务化的改造涉及 re-architecturing 等因素。

    云原生被企业接受之后,落地的过程需要解决这些挑战。运维管理含有丰富组件并快速演进的云原生的基础设施也对企业 IT 人员的技术技能提出了更高的要求。但是我们坚信,云原生技术带来的资源成本降低,研发运维效率提升等巨大价值,会驱动企业迎接这些挑战。

    在这个过程中,使用云原生上云,基于容器和服务网格等标准界面和混合云方案,将极大的降低迁云复杂度,使企业可以更快迁移到云上标准服务。**通过云原生上云最大化使用云的能力,高效的社会分工,使企业聚焦于自身业务发展,相信将成为企业的共识。
    **

    在企业迈入云原生的历程中,阿里云原生也将提供最大的助力。阿里云拥有国内最丰富的的云原生产品家族,最全面的云原生开源贡献,最大规模的云原生应用实践,最广泛的云原生客户群体,助力企业数字化转型。全链路压测、极速弹性扩缩容以及云原生的全栈技术已广泛服务于互联网、金融、零售、制造、政务等领域企业和机构,大幅降低了应用开发的门槛,让企业轻松享受云的技术红利。

    对于云原生从业者来说,最大的挑战之一就是兑现新技术给业务带去的价值。站在新的一个十年起点,云原生从业者应当坚定自己对于新技术价值的理解和洞察,沉下心去将云原生的基础能力建设好,创造行业趋势,为云计算的发展做出自己的贡献。

    今年是云原生编程挑战赛的第一年,是一个非常好的开始。我们看到越来越多年轻的技术力量融入到云原生的技术浪潮里,因为相信,所以看见。这些跳动的思考、新鲜的血液、创新的方案也将成为中国云原生技术发展生生不息的动力源泉。

    决赛答辩方案介绍:
    https://tianchi.aliyun.com/forum/?spm=5176.12282029.0.0.72d86fdcIExKCw#raceId=231793

    点击决赛答辩,观看云原生编程挑战赛决赛答辩盛况。

    ]]>
    技术与业务同行:我是如何在业务中成长的? Fri, 20 Jun 2025 02:20:33 +0800 作者:慕扉

    应用实时监控服务ARMS(Application Real-Time Monitoring Service)是一款应用性能管理(APM)产品,包含应用监控、Prometheus监控和前端监控三大子产品,涵盖分布式应用、容器环境、浏览器、小程序、App 等领域的性能管理,能帮助用户实现全栈式性能监控和端到端全链路追踪诊断,让应用运维从未如此轻松高效。

    我主要负责阿里云ARMS前端监控平台,该业务更偏向于技术类产品。我想聊聊如何在业务中成长,期间也有困惑和迷茫,希望我的经历或者方式方法能给有类似情况的前端同学有所帮助。

    我个人的成长主要分为三个阶段,分别是:

    (1)监控领域初接触,建立自身监控知识体系
    (2)业务痛点跟进,打造监控平台核心能力
    (3)业务场景不断拓展,建立保障业务稳定体系

    监控领域初接触,建立自身监控知识体系

    最初业务面临的问题:业务上线之后,用户在实际访问时遇到错误,业务方无法快速感知;发生线上故障后,很多场景无法快速复现和排查原因等。基于业务的这些痛点,团队决定搭建前端监控平台来解决这些问题。

    我是从Retcode2.0正式开始接触前端监控领域,面对一个新的领域,需要快速从0-1建立自身监控知识体系。这个过程是非常充实且充满挑战的,但当你完成这个阶段后会非常有成就感。面对未知和挑战,这里总结一下我认为比较重要的经验。

    勇于打破自己的边界,拓展自己的技术栈

    前端监控的整个体系简单总结一下:采集、日志存储、日志切分&计算、数据分析、告警,也就是工作不再局限于前端业务的开发工作,需要有Nginx服务运维能力、实时/离线分析能力、Node应用开发运维能力等等,所以我迈出了第一步,从前端->全栈的转变,让我整体熟悉并能把控前端监控从采集到最后告警诊断整个流程,在这个基础上让我后续能Cover整个监控平台。

    Owner意识

    对于负责的产品需要具备较强的Owner意识,把工作做大做强,服务好客户。每一个功能的开发、迭代、优化及创新,认真对待,保障每个环节做到最好。在这个过程中,我的角色也发生了改变,从最初的功能实现落地者到产品能力的主导技术方案的选型拍板者,这段时间的复盘让我不经意间意识到自己的这些变化。

    以我自己的一个经历为例:最初日志服务器的部署是运维同学直接在机器上配置好,再提供服务。我接手后就遇到了一个比较大的问题:扩容。正常应用扩容是很简单的事情,通过PSP提交扩容申请单,可快速完成扩容。但当前Nginx日志服务没有基线配置,无法直接PSP扩容,只能手动配置。

    对于扩容来说,当前的方案存在2个问题:

    (1)手动配置扩容时间成本高
    (2)无法有效保证所有机器上各类包的版本号一致。

    为了解决这些问题,就需要了解Nginx日志服务的能力及运维相关的能力,通过与PE、后端同学讨论,最终决定采用Dokcer的形式解决当时扩容的问题,不仅提升了运维效率,也为后续海外业务支持打好了基础。
    所以给到大家的建议是,不要单纯的完成产品的一个个功能,而是要有Owner意识,认真审视业务面临的问题,能够主动去跟进和改变,慢慢积累后续会产生质变。

    业务痛点跟进,打造监控核心能力

    平台从0-1搭建完成后,为了服务更多的业务,打磨产品能力,正式上云升级为阿里云ARMS前端监控平台。监控的基础能力已全部上线,后续如何发展是我需要思考的问题。如果后续在这个基础上一直做迭代优化,产品和个人都没有明显的突破与成长。

    针对技术类产品,大部分是技术同学主导,在产品发展到一个阶段后,就会面临如何做后续的突破问题。我有两点建议:

    (1)深入业务面临的问题,制定技术解决方案。

    首先问自己几个问题:
    • 业务方是谁?
    • 现在业务在用自己的产品的时候都有哪些问题?
    • 业务的诉求是什么?
    • 自己的产品存在哪些问题?

    深入挖掘这些问题,列出TOP5的答案,会发现有很多值得去做和突破的事情。

    在最初的前端监控领域,产品都集中在针对采集上报的数据做统计展示阶段,通过数据指标的波动情况发现异常,然后接下来异常的定位则直接依赖于原始日志,原始日志如果覆盖不到信息,则只能靠业务同学自己复现和排查了。更多时候统计的数据无法解释,直接导致业务同学对数据的准确性产生质疑。所以监控产品要从最初的数据统计演进为问题定位。在这个阶段,主导并补齐相应的问题诊断链路。

    (2)拓展视野 (技术&业务)

    在主导一个产品方案/制定技术方案前,需要提前调研,辅助做出决策。调研的目的是拓展自己的技术&业务视野,其中对应的途径可以有:

    • 竞品分析:当前成熟的产品听云、dynatrace、oneAPM等;

    • 相关联领域同学输入/讨论:产品、后端应用监控同学等。

    一个线上问题的排查,不是独立的前端监控或者应用监控就直接给到原因的,拓展自己认知的领域后,与后端中间件同学讨论,最终制定提供全链路监控的方案,来满足业务排查问题的诉求。通过这个案例可以看到,如果不跨出一步,是看不到也无法给出方案的。

    业务场景不断拓展,建立保障业务稳定体系

    在产品能力整体趋于稳定后,如何寻找自己的突破口?我也曾经走过误区,希望自己在技术上能有突破,所以出发点是现在有哪些技术可以在我的产品上体现出深度,直接导致越考虑越迷茫。其实,正确的出发点仍然是第二部分提到的:从业务痛点出发来制定解决方案。在这一部分不再是单点解决问题,而是体系化的考虑方案。

    我有几点经验可以分享下:

    开放的心态,合作共赢

    技术类产品会收到各个业务方的诉求,在人力有限的情况下要支持各类诉求难度很大。这时候摆正心态,可以拉诉求方同学合作共建,更好的满足业务方诉求,同时让产品也不断拓展支持场景。

    前端技术发展非常迅速,在最初小程序迅猛发展的时候,小程序的监控诉求也随之而来。但当时团队对于小程序的技术架构等并不熟悉,在此基础上做监控成本较大。其中钉钉有较多的访问量级较大的小程序,对于监控有较强的的诉求,在综合考虑业务诉求和产品拓展后,与钉钉同学合作共建支持各类小程序的监控诉求。在这次合作中,让我深刻体会到优势互补、事半功倍的效果。

    体系化建设

    在前期完成对于整个体系的了解,后续可以从这个体系涉及的各个环节来综合考虑解决方案。

    随着业务的不断接入,监控所需的计算资源、存储资源等问题不断暴露出来,这时候我的工作不仅要保障业务稳定,更要保障平台稳定,所以在采集阶段、上报阶段、存储阶段、计算阶段考量制定保障方案。完成体系化稳定性建设后,不仅可以在大促前1分钟发现风险,也可以保障平台稳定支持大促中各类站点的监控诉求,并且在大促后沉淀赋能于日常的稳定性运维工作。

    结束语

    每个人的经历与负责的工作各不相同,无法直接照搬别人成功的经验,同时很多总结的点都是知易行难,但可以从优秀同学的经验及总结中找到自己认可的内容,坚持并不断在自己身上实践,只有不断实践才能慢慢转化为自己的能力。

    推荐文档:
    阿里云业务实时监控服务ARMS:https://www.aliyun.com/product/arms
    阿里云业务实时监控服务ARMS前端监控:https://arms.console.aliyun.com/#/retcode
    阿里云业务实时监控服务ARMS前端监控文档:https://help.aliyun.com/document_detail/58652.html

    ARMS是阿里云原生团队非常重要的一款产品。目前已经服务了如人人视频、完美日记、畅捷通等众多客户,云原生中间件的技术和产品体系,如何帮助企业降低业务的运行成本和技术风险?如何提升业务的迭代速度?针对云原生场景下常见的技术挑战和痛点,阿里云、人人视频、畅捷通技术专家有哪些技术经验和思考?我们将在9月18日13:00 云栖大会「云原生中间件」全面揭秘!

    中间件分论坛(二维码).jpg

    扫码或点击阅读原文预约直播,获取云原生中间件的实战经验和落地思考。
    阅读原文:https://yunqi.aliyun.com/2020/session91

    ]]>
    解构云原生,从概念到落地:阿里云、声网、微博、好未来、CNCF的专家们怎么看? Fri, 20 Jun 2025 02:20:33 +0800

    钉钉2小时内扩容1万台云主机支撑2亿上班族在线开工,申通快递核心系统云原生化上云,日均处理订单3000万,IT成本降低50%,中国联通建成最大云上BSS系统支持3.6亿用户无缝覆盖,完美日记采用容器服务ACK,节省服务器成本50%以上,轻松应对大促……

    这些案例的背后正是云原生的普及,推动全社会加速享受技术红利。 从2009年首次上线核心中间件系统,到2011年淘宝天猫开始使用容器调度技术,再到推出自研云原生硬件神龙服务器、云原生数据库PolarDB,阿里已经在云原生领域深耕数十年。2019年双11之前,阿里核心系统完成100%上云,这也是全球规模最大的云原生实践。

    目前,阿里云已将基础设施全面升级云原生。关于云原生,我们曾在年初发布了:2020 云原生7大趋势预测。对于云原生从业者而言,2020年最大的挑战就是兑现新技术给业务带去的价值,那么过去一年,阿里云原生取得了哪些成果?又有哪些企业接受了云原生的技术理念从而加速业务升级?

    让我们在云栖大会做一次整体的汇报。作为今年云栖大会的重磅热点之一,云原生有哪些新玩法?

    四大分论坛,拆解云原生技术实践

    分论坛一:企业云原生创新与实践

    以前一家企业想使用云原生的技术或产品,需要花费大量的精力研究一些开源项目,自己做运维和管理,还需要考虑集成、稳定性保障等问题,这样才能建立一个云原生平台。今天,为了方便企业和开发者更容易地使用云原生的技术和产品,更好地接受云原生的理念,并解决企业担忧的可靠性、性能、连续性等问题,阿里云为大家提供了一整套云原生产品家族,提供了非常强的 SLA 保障。

    18 企业云原生-商圭.png

    在企业云原生创新与实践分论坛,不仅有阿里云技术专家分享容器技术、Serverless容器、云原生基础设施、底层系统等产品升级和发展演进,还邀请了来自好未来、声网、新浪微博、CNCF的技术专家分享云原生实践的历程、开源项目和经验思考。

    分论坛二:云原生中间件

    如果把企业内部的业务比喻为一个城市系统,这个城市中的IT机构就是像水、电、煤一样的基础设施,那么中间件就类似于输水管道,推动着数据从一个应用流向另一个应用。而在云计算时代,中间件又被赋予了新的定义,那就是对云原生的支持。

    中间件海报.png

    本论坛将全面解读如何利用阿里云原生中间件的技术和产品体系,帮助企业降低业务的运行成本和技术风险,提升业务的迭代速度。同时,针对云原生场景下常见的技术挑战和痛点,分享技术经验和思考,并深入探讨云原生中间件如何加速企业数字化转型等热点话题。

    分论坛三:Serverless,引领云的下一个十年
    试想一下,如果服务器的概念被进一步抽象,那么与服务器有关的维护工作都会变成由云来承担。这就是我们常说的Serverless。过去十年,云正在逐步向 Serverless 演进。阿里云最初发布的 ECS 是服务器抽象,随着云原生技术的发展,Docker 容器让应用运行变得简单,Kubernetes 让集群运维变得简单。

    serverless海报.png

    Serverless将开发人员从繁重的手动资源管理和性能优化中解放出来,就像数十年前汇编语言演变到高级语言的过程一样,云计算生产力再一次发生变革。与其说Serverless是云计算的升华,不如说Serverless重新定义了云计算,将成为云时代新的计算范式,引领云的下一个十年。对于Serverless的热点话题,我们在Serverless分论坛邀请众多大咖一起来碰撞新思考。

    分论坛四:企业数字化转型最佳实践

    云原生,是云计算的再升级,也是企业数字化转型的最短路径。阿里巴巴作为中台概念提出者和践行者,积极推动中台发展,并完整提出从理论到实践的企业数字化转型最佳路径。本论坛将介绍业务中台技术解决方案产品为基础,围绕服务能力的标准化、可复用、可扩展、可视化、可管控等要素提供新方法和新工具,帮助业务中台实施落地。

    业务中台海报.png

    一场Serverless重磅新品发布

    在云栖大会新品发布会上,阿里云智能研究员丁宇将重磅发布4个Serverless生态产品,助力阿里云Serverless的快速发展。

    • 沙箱容器2.0:阿里云Serverless产品的基石,更稳定、更安全、更弹性。
    • EventBridge:云上事件枢纽,原生支持CloudEvents,更标准、更规范。
    • Serverless工作流:提供简单灵活、可视化的函数编排,更直观、更便捷。
    • 函数计算2.0Plus:携手开发者工具+应用中心,引领开发者体验全面升级。

    叔同海报22.jpg

    在9月18日13:00 云栖大会上,丁宇将详细讲解四大产品的特性和应用实践,扫码订阅直播,第一时间获得直播开始通知。

    一场面向生态合作伙伴的分享

    随着阿里云在云原生领域的探索和实践,阿里云逐渐成为云原生领域的定义者,领导者,对云原生技术发展做了一些预判:

    • 我们相信,云原生技术成为了云计算的新界面;
    • 我们相信,云原生技术能够重塑软件研发全生命周期;
    • 我们相信,云原生技术能够推动了信息产业的转型与升级;

    因此说云原生是释放云计算技术红利的最短路径;这次阿里云正式发布的云原生联盟,是阿里云云原生合作伙伴计划的重要载体。以解决方案伙伴为驱动,带动分销伙伴、服务伙伴,实现三位一体,帮助伙伴在销售能力、产品和解决方案能力、服务能力的全面成长。坚持定位云原生基础设施,“练好内功、做好被集成,帮助伙伴构建自身核心竞争力,共同服务企业数字化转型”。

    宁晓民.jpg

    9月17日13:00,阿里云智能资深解决方案架构师宁晓民将分享云原生合作伙伴计划的发展与目标。

    附上云栖大会云原生参会指南,一图解锁云原生重要热点和重大发布。

    参会秘籍.jpg

    敬请期待 9.18云栖大会-云原生四大分论坛精彩内容。点击 订阅 提前订阅直播。

    ]]>
    ARMS在APM工具选型中的实践 Fri, 20 Jun 2025 02:20:33 +0800 前言

    当前的系统在数字化转型需求以及互联网架构实施的影响下,越来越普遍地使用了微服务架构,我们在享受微服务带来的好处(开发效率高, 独立部署, 水平扩展, 故障与资源隔离等等)外,也带来测试,事务,应用监控等各方面的困难。

    image.png
    从上图可以看出,在以分布式为主的互联网架构下,应用间的调用变得越来越复杂,我们传统使用的开发工程师主动埋点,运维人员到主机上查日志,组合调用链,监控应用的运行情况,显得越来越力不从心。

    为了更好地做到应用层面的监控,包括应用运行环境的基础设施数据,系统业务调用情况,性能消耗分析,在发生性能,异常与故障问题时,能够快速定位和解决问题,诞生了很多优秀的APM(Application Performance Management)工具。

    这些APM工具都提供了包括指标统计信息与调用链路跟踪信息。

    常见的APM工具

    APM工具包括指标收集与调用链收集。指标收集例如在某一段时间的请求数,异常数,错误数,响应时间RT, IAAS层的资源使用情况(例如cpu, memory, IO, load, 网络), 也包括JVM的各种运行参数(例如 各内存分区情况,gc情况)。调用链收集包括业务请求中访问过的各应用,类,方式,在每个运行节点/方法上的时间消耗情况。
    常见的APM工具有:
    1、ARMS:由阿里巴巴自研开发的一款APM工具。由于分布式微服务框架以阿里为主体的企业很早就开始探索,阿里集团内很早就有配套的鹰眼系统做相关的应用监控,为适应产品上云输出,阿里在2016-08-04的时间就以ARMS的产品形式正式对外提供应用监控服务。
    2、开源系的APM
    u Pinpoint:基于java编写的开源APM工具,由韩国人开发贡献,功能完善,发展快,影响了很多其它的APM工具实现,在国内外使用比较广泛。
    u Skywalking:支持open tracing标准,由我国的吴晟主导开发的分布式追踪,分析,告警的开源工具,当前是Apache旗下的开源项目,发展非常迅速,在各类开源APM工具里国内的使用比较广泛。
    u ZipKin:支持open tracing标准,由Twitter公司开发贡献,于2012年的时候就开始开源发展,是比较成熟的开源APM工具。
    u Jaeger:支持open tracing标准,由Uber公司开发贡献,是比较成熟的开源APM工具。

    APM工具原理

    尽管这些APM工具功能与实现各有不同,但基本上原理都是一样的,这个原理基于google dapper的分布式追踪技术论文,把APM工具实现总体上分为两大部分:
    1、对应用运行节点上进行应用埋点,在业务运行期间进行埋点数据的生成;

    image.png
    2、通过APM的后端服务日志收集,数据清洗与聚合,把相应的处理结果持久化,并且提供丰富的可视化控制台。
    image.png

    在这个调用链追踪技术里,还原调用链的功能主要依赖于两个ID.

    第一个ID是TraceID, 这个代表一个业务调用,就好像在电商系统里发起的一个下单结算; 在线教育里的一个选课流程; 物流系统里的揽收; 这些业务从客户触发到获得响应结果就是一个完整的请求,就是一次业务调用,它每一次的业务请求的都会获得维一的TraceID;

    第二个ID是RpcID (或者称为SpanID), 在一次业务请求中,可能经过的应用会有多过,以一个电商下单业务为例: 它需要经过订单系统创建订单; 支付系统接受支付;库存系统扣减产品库存;会员系统给买家进行积分处理; 购物车系统会清理购物清单。这样对于业务流经的每一个应用,都有一个有层次的RpcID, 这个RpcID可以认为是使用目录层级记录的,从这个RpcID来看,那怕它在同一个业务中被调用了多次,它的每一次进入的RpcID都是一样的。

    依赖于TraceID & RpcID,我们可以很方便地还原整个调用链。
    image.png

    ARMS功能上的优势:

    客观来说, 优秀的APM工具发展到现在,基础功能上的差异都不大。例如以前开源APM比较薄弱的自动埋点功能也跟进了ARMS这些先发的产品; 在异步产品如各类MQ的支持上也慢慢拉平;SQL/API参数抓取的功能方面也是补足。我们再来列一下ARMS的优点:

    1、指标数据的准确性
    ARMS的agent把指标数据与调用链数据是分开两种类型来采集统计的, 相应的指标数据不受调用链的采样率的影响,会在具体的运行节点进行完完全全的统计后,精准到上传加载到ARMS后端。(而有些优秀的APM工具是通过采样上来的调用链进行加工处理,再来产出相应的指标,这在准确性上会有一定的丢失。)
    2、线程栈捕获
    因为是JAVA自动埋点的原理是对已知的框架进行字节码加强,当某框架不在已支持的范围内,那么这段访问的信息就不会被记录下来。ARMS可以通过设定调用超过一定时长后,可以通把整个线程栈捕获下来,这样我们就可以通过线程栈的分析进行补充定位。
    3、线程分析
    ARMS可以通过线程分析页签,清淅地看到各类线程的资源占用情况。例如可以知道当前的某线程池线程数是多少, 占用cpu最多的线程是那个, 占用的百分比情况,并且可以看到线程的运行状态。
    4、业务关联日志
    ARMS这边可以通过给合传统的log4j等技术,在输出业务日志上可以把相应的TraceID 就像线程ID那样通过方便的配置就可以与业务日志同时输出。另外,ARMS与阿里云的SLS进行整合,可以通过ARMS的页面方便地根据调用链的TraceID查找到关联的业务日志,这样需要结合业务日志定位时,更方便实用。
    5、智能合并能力
    ARMS对于相同的调用,例如递当,循环会进行智能合并,显示循环的次数,执行的最大时长,最小时长,平均时长。
    image.png
    6、主动诊断能力
    ARMS提供了主动诊断能力,可以通过选定具体的时间,执行主动诊断,ARMS会分析这一段时间内的应用运行情况,自动总结这一段时间内的问题,并且结合阿里的经验,产出具体的报表。我们依据这个报表,可以加速我们的定位与优化。
    7、丰富的报警能力
    完善报警体系,ARMS提供了丰富的报警规则,我们可以对相应的规则进行开启/关停,编辑,这样可以快速搭建报警体系。在报警通道方面,可以直接发对接钉钉/WebHook/Email/短信网关等。

    运维能力上的优势

    1、 按需监控启停管理
    通过ARMS的管理控制台,我们可以批量在管理应用的启停,可以一键停止所有的ARMS监控,也可以一键启动相关应用的监控。非常符合上云的按需要使用观念。
    2、动态采样率变更
    在面对特殊的时间点或者异常出现机率的时候,我们希望动态调整采样率,例如通过调大采样率来捕获这些概率极少的调用链,借助ARMS的配置管理,我们可以非常方便地把更齐全的调用链收集起来;通过调小采样率来保证存储空间的合理使用(其它的APM工具在做采样率的变更时,需要应用的重新配置,启动,这不但处理起来麻烦,并且影响业务的边续性,在实际操作上很难下定决心去在运行期间中断业务去做改变采样率的变更。)
    3、 绑定参数的开关
    虽然很多APM工具都可以提供绑定参数的功能。但很多时候,如果对于业务数据敏感的系统,并不希望这类APM工具在非必要的时候采集SQL/API的运行参数。所以ARMS在它的配置管理里提供这么一个功能非常有意义,也就是当需要收集这些运行的业务参数进行问题定位分析的时候,那么只要打开就可以了,使用完毕后,再通过把这些开关关上,那么就可以保护我们的业务数据不外泄漏出去了。
    4、接入简易
    可通过更简易的方式如阿里容器ACK/EDAS/SAE等各种非常便捷的接入方式,只需要简单的YAML注解或按钮即可完成接入。
    5、 组件稳定免运维
    因为ARMS是商业化的产品,所以所有的组件都是不需要我们使用方运维的。如果使用开源自建,那么我们就需要对日志收集,计算清洗服务,存储产品本身进行运维,包括相应的集群规模,清理处理,扩容处理,如果在峰值过后,不进行资源回收,也会产生额外的使用浪费。

    成本使用上的优势

    1、 ARMS是按接入节点,接入的小时(时长)计费的,这样可以充分发挥云上产品的优势。按需要使用,按需要的应用节点付费。另外ARMS单纯地按照节点数来计算,并不受采样率的变动而产生变化,这样对于大采样率的应用是有一定的优势。
    2、 ARMS有相应的资源包,可以通过购买资源包的方式进一步节省费用。
    3、 因为产品的组合因素,ARMS如果搭配阿里云的容器(ACK)使用,计费会自动5折。

    最后,这里列一下开源与ARMS的一个成本的比较供大家参考:
    image.png

    备注

    1、开源的按照统一统计数据存15天,全量明细数据存3天计算(ARMS的数据是全天24小时使用,存储60天,在非容器下按年包折下来的月费用。)
    2、人力成本以具有开发能力的运维人员月薪3万计算。人力成本,主要参数变动带来的发布,后端系统的不稳定带来的效率损失,后端系统的维护操作。中大型的会做一些定制的开发(例如采样的动态配置化生效)

    综述

    在阿里云上,ARMS在APM层面提供了足够丰富的功能;可以友好地运维操作;另外通过合理地按需使用,结合资源包,以及容器的方式运行,使用起来还高效与节省。作为基础设施的实用监控,不重复发明轮子,不重人力资源投入,综合考虑各方面因素,最终选择使用ARMS。

    ]]>
    【升级】9月4日Afilias注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Afilias注册局维护通知】

    维护时间:北京时间 2020年9月4日 22:00 - 23:00

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .Red/.Kim/.Pro/.Asia/.Info/.Mobi/.ORG域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】9月11日消息队列AMQP升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列AMQP】【升级通知】

    升级窗口:北京时间2020年9月11日 00:00 - 03:00
    升级内容:华东1(杭州)、北京、深圳、上海、内蒙古、张家口、香港、青岛等全部区域(铂金版)服务升级。
    升级影响:升级期间消息队列AMQP相关服务访问可能会出现多次闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过 5 分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    权威认可!蚂蚁分布式金融核心套件bPaaS成金融核心系统的最佳实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 日前,由国家金融与发展实验室金融科技研究中心与神州信息共同发起,由金融科技50人论坛具体推动和落实的 “首届NIFD-DCITS全球金融科技创新案例”征集活动在“金融科技创新应用与发展”研讨会上公布成果。

    经过为期一年的征集评选,蚂蚁分布式金融核心套件bPaaS从112个来自国内外金融机构、科技企业的参选案例中脱颖而出,正式入选 “2020全球金融科技创新案例库”,并收录于《“新基建+数字金融”全球金融科技创新实践(2020)》一书中,已由中国金融出版社发行。

    0.jpg

    分布式金融核心套件bPaaS(Business Platform As a Service)是凝结了蚂蚁多年“大中台、小前台”架构实践沉淀的分布式金融核心能力,它依托蚂蚁集团的金融领域建模和微服务应用架构实践,可以帮助金融机构快速建立数字化对客服务和数字化运营能力,支撑其快速业务创新, 促进业务发展。

    以网商银行为例,作为中国第一家核心系统基于云计算架构的商业银行,网商银行基于分布式金融核心套件bPaaS的金融基础服务和基础组件,提供产品业务创新迭代所需要的整套技术服务,同时保障底层基础设施具备更高的稳定性、可用性及性能。在没有一个网点的情况下,截至目前,网商银行已为超过2000万家小微企业及个体经营者提供了金融服务。

    实际上,bPaaS的精髓就在于,以非常强大的可编排、可组合、可配置、可扩展的技术服务能力,来支撑业务的快速敏捷和灵活多变,让金融机构“复制蚂蚁的核心技术能力”成为现实。当前,bPaaS 已从对内提供服务开放给对外提供服务,将自带支付宝10余年来的技术发展和业务创新能力赋能金融机构,是分布式架构下金融核心系统的最佳实践。

    据悉,包括蚂蚁企业级分布式关系数据库OceanBase、金融级分布式架构SOFAStack、移动开发平台mPaaS和分布式金融核心套件bPaaS等在内的数十个系列、上百款产品和解决方案正通过阿里云新金融统一对外输出,服务各种类型的金融机构。未来,还会有越来越多的蚂蚁金融科技产品和解决方案通过阿里云新金融对外输出,全力助推金融客户完成数字化转型。

    ]]>
    论程序的健壮性——就看Redis-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    “众里寻他千百度,蓦然回首,那人却在,灯火阑珊处”。多年的IT生涯,一直希望自己写的程序能够有很强的健壮性,也一直希望能找到一个高可用的标杆程序去借鉴学习,不畏惧内存溢出、磁盘满了、断网、断电、机器重启等等情况。但意想不到的是,这个标杆程序竟然就是从一开始就在使用的分布式缓存——Redis。


    Redis(Remote Dictionary Server ),即远程字典服务,是 C 语言开发的一个开源的高性能键值对(key-value)的内存数据库。由于它是基于内存的所以它要比基于磁盘读写的数据库效率更快。因此Redis也就成了大家解决数据库高并发访问、分布式读写和分布式锁等首选解决方案。

    那么既然它是基于内存的,如果内存满了怎么办?程序会不会崩溃?既然它是基于内存的,如果服务器宕机了怎么办?数据是不是就丢失了?既然它是分布式的,这台Redis服务器断网了怎么办?

    今天我们就一起来看看Redis的设计者,一名来自意大利的小伙,是如何打造出一个超强健壮性和高可用性的程序,从而不惧怕这些情况。

    一、 Redis的内存管理策略——内存永不溢出

    Redis主要有两种策略机制来保障存储的key-value数据不会把内存塞满,它们是:过期策略和淘汰策略。

    1、 过期策略

    用过Redis的人都知道,我们往Redis里添加key-value的数据时,会有个选填参数——过期时间。如果设置了这个参数的值,Redis到过期时间后会自行把过期的数据给清除掉。“过期策略”指的就是Redis内部是如何实现将过期的key对应的缓存数据清除的。

    在Redis源码中有三个核心的对象结构:redisObject、redisDb和serverCron。

    • redisObject:Redis 内部使用redisObject 对象来抽象表示所有的 key-value。简单地说,redisObject就是string、hash、list、set、zset的父类。为了便于操作,Redis采用redisObject结构来统一这五种不同的数据类型。

    • redisDb:Redis是一个键值对数据库服务器,这个数据库就是用redisDb抽象表示的。redisDb结构中有很多dict字典保存了数据库中的所有键值对,这些字典就叫做键空间。如下图所示其中有个“expires”的字典就保存了设置过期时间的键值对。而Redis的过期策略也是围绕它来进行的。

    • serverCron:Redis 将serverCron作为时间事件来运行,从而确保它每隔一段时间就会自动运行一次。因此redis中所有定时执行的事件任务都在serverCron中执行。

    了解完Redis的三大核心结构后,咱们回到“过期策略”的具体实现上,其实Redis主要是靠两种机制来处理过期的数据被清除:定期过期(主动清除)和惰性过期(被动清除)。

    • 惰性过期(被动清除):就是每次访问的时候都去判断一下该key是否过期,如果过期了就删除掉。该策略就可以最大化地节省CPU资源,但是却对内存非常不友好。因为不实时过期了,原本该过期删除的就可能一直堆积在内存里面!极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
    • 定期过期(主动清除):每隔一定的时间,会扫描Redis数据库的expires字典中一定数量的key,并清除其中已过期的 key。Redis默认配置会每100毫秒进行1次(redis.conf 中通过 hz 配置)过期扫描,扫描并不是遍历过期字典中的所有键,而是采用了如下方法:

    (1)从过期字典中随机取出20个键;
    (server.h文件下ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP配置20)

    (2)删除这20个键中过期的键;

    (3)如果过期键的比例超过 25% ,重复步骤 1 和 2;

    具体逻辑如下图:

    因为Redis中同时使用了惰性过期和定期过期两种过期策略,所以在不同情况下使得 CPU 和内存资源达到最优的平衡效果的同时,保证过期的数据会被及时清除掉。

    2、淘汰策略

    在Redis可能没有需要过期的数据的情况下,还是会把我们的内存都占满。比如每个key设置的过期时间都很长或不过期,一直添加就有可能把内存给塞满。那么Redis又是怎么解决这个问题的呢?——那就是“淘汰策略”。

    官网地址:https://redis.io/topics/lru-cache
    Reids官网上面列出的淘汰策略一共有8种,但从实质算法来看只有两种实现算法,分别是LRU和LFU。

    LRU(Least Recently Used):翻译过来是最久未使用,根据时间轴来走,淘汰那些距离上一次使用时间最久远的数据。
    LRU的简单原理如下图:

    从上图我们可以看出,在容器满了的情况下,距离上次读写时间最久远的E被淘汰掉了。那么数据每次读取或者插入都需要获取一下当前系统时间,以及每次淘汰的时候都需要拿当前系统时间和各个数据的最后操作时间做对比,这么干势必会增加CPU的负荷从而影响Redis的性能。Redis的设计者为了解决这一问题,做了一定的改善,整体的LRU思路如下:

    (1)、Redis里设置了一个全局变量 server.lruclock 用来存放系统当前的时间戳。这个全局变量通过serverCron 每100毫秒调用一次updateCachedTime()更新一次值。

    (2)、每当redisObject数据被读或写的时候,将当前的 server.lruclock值赋值给 redisObject 的lru属性,记录这个数据最后的lru值。

    (3)、触发淘汰策略时,随机从数据库中选择采样值配置个数key, 淘汰其中热度最低的key对应的缓存数据。

    注:热度就是拿当前的全局server.lruclock 值与各个数据的lru属性做对比,相差最久远的就是热度最低的。

    Redis中所有对象结构都有一个lru字段, 且使用了unsigned的低24位,这个字段就是用来记录对象的热度。

    LFU(Least Frequently Used):翻译成中文就是最不常用。是按着使用频次来算的,淘汰那些使用频次最低的数据。说白了就是“末尾淘汰制”!
    刚才讲过的LRU按照最久未使用虽然能达到淘汰数据释放空间的目的,但是它有一个比较大的弊端,如下图:

    如图所示A在10秒内被访问了5次,而B在10秒内被访问了3 次。因为 B 最后一次被访问的时间比A要晚,在同等的情况下,A反而先被回收。那么它就是不合理的。LFU就完美解决了LRU的这个弊端,具体原理如下:

    上图是末尾淘汰的原理示意图,仅是按次数这个维度做的末尾淘汰,但如果Redis仅按使用次数,也会有一个问题,就是某个数据之前被访问过很多次比如上万次,但后续就一直不用了,它本身按使用频次来讲是应该被淘汰的。因此Redis在实现LFU时,用两部分数据来标记这个数据:使用频率和上次访问时间。整体思路就是:有读写我就增加热度,一段时间内没有读写我就减少相应热度。

    不管是LRU还是LFU淘汰策略,Redis都是用lru这个字段实现的具体逻辑,如果配置的淘汰策略是LFU时,lru的低8位代表的是频率,高16位就是记录上次访问时间。整体的LRU思路如下:

    (1)每当数据被写或读的时候都会调用LFULogIncr(counter)方法,增加lru低8位的访问频率数值;具体每次增加的数值在redis.conf中配置默认是10(# lfu-log-factor 10)

    (2)还有另外一个配置lfu-decay-time 默认是1分钟,来控制每隔多久没人访问则热度会递减相应数值。这样就规避了一个超大访问次数的数据很久都不被淘汰的漏洞。

    小结:“过期策略” 保证过期的key对应的数据会被及时清除;“淘汰策略”保证内存满的时候会自动释放相应空间,因此Redis的内存可以自运行保证不会产生溢出异常。

    二、 Redis的数据持久化策略——宕机可立即恢复数据到内存

    有了内存不会溢出保障后,我们再来看看Redis是如何保障服务器宕机或重启,原来缓存在内存中的数据是不会丢失的。也就是Redis的持久化机制。

    Redis 的持久化策略有两种:RDB(快照全量持久化)和AOF(增量日志持久化)

    1、 RDB

    RDB 是 Redis 默认的持久化方案。RDB快照(Redis DataBase),当触发一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件dump.rdb。Redis重启会通过dump.rdb文件恢复数据。那那个一定的条件是啥呢?到底什么时候写入rdb 文件?

    触发Redis执行rdb的方式有两类:自动触发和手动触发
    “自动触发”的情况有三种:达到配置文件触发规则时触发、执行shutdown命令时触发、执行flushall命令时触发。

    注:在redis.conf中有个 SNAPSHOTTING配置,其中定义了触发把数据保存到磁盘触发频率。

    “手动触发”的方式有两种:执行save 或 bgsave命令。执行save命令在生成快照的时候会阻塞当前Redis服务器,Redis不能处理其他命令。如果内存中的数据比较多,会造成Redis长时间的阻塞。生产环境不建议使用这个命令。

    为了解决这个问题,Redis 提供了第二种方式bgsave命令进行数据备份,执行bgsave时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。

    具体操作是Redis进程执行fork(创建进程函数)操作创建子进程(copy-on-write),RDB持久化过程由子进程负责,完成后自动结束。它不会记录 fork 之后后续的命令。阻塞只发生在fork阶段,一般时间很短。手动触发的场景一般仅用在迁移数据时才会用到。

    我们知道了RDB的实现的原理逻辑,那么我们就来分析下RDB到底有什么优劣势。

    优势:

    (1)RDB是一个非常紧凑(compact类型)的文件,它保存了redis在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。

    (2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

    (3)RDB在恢复大数据集时的速度比AOF的恢复速度要快。

    劣势:

    RDB方式数据没办法做到实时持久化/秒级持久化。在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照之后的所有修改

    2、 AOF(Append Only File)

    AOF采用日志的形式来记录每个写操作的命令,并追加到文件中。开启后,执行更改 Redis数据的命令时,就会把命令写入到AOF文件中。Redis重启时会根据日志文件的内容把写指令从前到后执行一次以完成数据的恢复工作。

    其实AOF也不一定是完全实时的备份操作命令,在redis.conf 我们可以配置选择 AOF的执行方式,主要有三种:always、everysec和no

    AOF是追加更改命令文件,那么大家想下一直追加追加,就是会导致文件过大,那么Redis是怎么解决这个问题的呢?
    Redis解决这个问题的方法是AOF下面有个机制叫做bgrewriteaof重写机制,我们来看下它是个啥

    注:AOF文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF文件。

    我们知道了AOF的实现原理,我们来分析下它的优缺点。

    优点:

    能最大限度的保证数据安全,就算用默认的配置everysec,也最多只会造成1s的数据丢失。

    缺点:

    数据量比RDB要大很多,所以性能没有RDB好!

    小结:因为有了持久化机制,因此Redis即使服务器宕机或重启了,也可以最大限度的恢复数据到内存中,提供给client继续使用。

    三、Redis的哨兵模式——可战到最后一兵一卒的高可用集群

    内存满了不会挂,服务器宕机重启也没问题。足见Redis的程序健壮性已经足够强大。但Redis的设计者,在面向高可用面前,仍继续向前迈进了一步,那就是Redis的高可用集群方案——哨兵模式。

    所谓的“哨兵模式”就是有一群哨兵(Sentinel)在Redis服务器前面帮我们监控这Redis集群各个机器的运行情况,并且哨兵间相互通告通知,并指引我们使用那些健康的服务。

    Sentinel工作原理:

    1、 Sentinel 默认以每秒钟1次的频率向Redis所有服务节点发送 PING 命令。如果在down-after-milliseconds 内都没有收到有效回复,Sentinel会将该服务器标记为下线(主观下线)。

    2、 这个时候Sentinel节点会继续询问其他的Sentinel节点,确认这个节点是否下线, 如果多数 Sentinel节点都认为master下线,master才真正确认被下线(客观下线),这个时候就需要重新选举master。

    Sentinel的作用:

    1、监控:Sentinel 会不断检查主服务器和从服务器是否正常运行

    2、故障处理:如果主服务器发生故障,Sentinel可以启动故障转移过程。把某台服务器升级为主服务器,并发出通知

    3、配置管理:客户端连接到 Sentinel,获取当前的 Redis 主服务器的地址。我们不是直接去获取Redis主服务的地址,而是根据sentinel去自动获取谁是主机,即使主机发生故障后我们也不用改代码的连接!

    小结:有了“哨兵模式”只要集群中有一个Redis服务器还健康存活,哨兵就能把这个健康的Redis服务器提供给我们(如上图的1、2两步),那么我们客户端的链接就不会出错。因此,Redis集群可以战斗至最后一兵一卒。

    这就是Redis,一个“高可用、强健壮性”的标杆程序!

    作者:宜信技术学院 谭文涛

    ]]>
    阿里云多账号网络互通最佳实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 前言

    在企业起步阶段,规模较小,一般采用单账号模式。随着企业的发展,单账号的缺陷越来越明显,多账号相对于单账号有众多优点:
    • 多账号间的资源默认隔离,减少了单账号因为一个资源或服务问题导致其它资源和服务也出现问题的可能性;
    • 多账号减少了单帐户过于宽泛的 RAM 权限带来的风险;
    • 多账号便于成本结算、独立管理、环境隔离等。

    因此中大型企业上云时通常选择多账号,但是多账号间往往存在着大量的网络互通场景,如何解决多账号的网络互通问题呢?
    VPC 作为云上最常用的网络环境,不同账号的 VPC 之间默认是无法互通的,多账号网络互通常用的解决方案是 CEN(云企业网)和 VPN 网关。

    基于 VPN 的网络架构

    VPN 网关是一款基于 Internet 的网络连接服务,通过加密通道的方式实现企业数据中心、企业办公网络或 Internet 终端与 VPC 安全可靠的连接。VPN 网关可以实现跨地域、跨账号的 VPC 互通,在需要连接的 VPC 上创建 VPN 网关,网关之间通过基于 Internet 的 IPSec 加密隧道来传输私网数据,以实现安全可靠的多账号 VPC 间通信。

    图1.png

    图1

    如上图所示:服务分别部署在账号1、2、3的 VPC 网络环境中,每个服务基于多可用区和 SLB 实现同城双活,前端 VPC 部署前端服务,后端 VPC 部署后端应用,前端服务处理 Web 请求时需调用部署在账号2、账号 3 的后端应用,分别为每个 VPC 创建 VPN网关,VPN 之间配置 IPSEC、路由,以此来实现多账号网络互通。

    基于 CEN 的网络架构

    云企业网(Cloud Enterprise Network)是承载在阿里云提供的高性能、低延迟的私有全球网络上的一张高可用网络,可以在跨地域、跨账号的 VPC 间搭建私网通道,通过自动路由分发及学习,提高网络的快速收敛和跨网络通信的质量和安全性,实现全网资源的互通,打造一张具有企业级规模和通信能力的互联网络。

    图2.png

    图2

    如上图所示:首先创建 CEN 实例,将要互通的网络实例(专有网络和边界路由器)加载到 CEN 中,再购买一个带宽包(同 Region 无需购买带宽包),配置路由、跨账号授权等,即可实现服务在不同账号的 VPC 间互通。

    VPN 和 CEN 网络架构比较

    VPN 和 CEN 都可实现多账号网络互通,两者有何区别呢?
    • VPN 需要为每个 VPC 配置 VPN 网关,创建 IPsec 连接、配置 VPN 网关路由等,随着 VPC 数量的增加,人工配置成本成倍增加;同时 VPN 连接使用共享的公网资源进行通信,网络延时和丢包率等都无法保证,其网络带宽受限于公网 IP 的带宽。在实际使用中 VPN 网关很少用于多账号网络互通,多用于本地数据中心与 VPC 互通以构建混合云。
    • CEN 专线连接在网络质量、安全性和传输速度等方面都优于 VPN,但是 CEN 在跨账号连接时需要配置跨账号授权、路由等,存在一定的配置成本;跨 Region 通信时需要购买带宽,存在一定的费用成本;每个 CEN 实例在每个 Region 可加载的网络实例数量有限。

    由此可见,VPN 和 CEN 都能解决多账号网络互通问题,但是不一定适合所有的场景,还有其他的方案吗?

    ResourceSharing 介绍

    不同于 VPN 和 CEN,资源共享服务(ResourceSharing)通过在账号间共享 VSwitch 来实现多账号网络互通。
    如下图所示:企业账号 A、B、C、D 加入 ResourceDirectory(资源目录)后,资源所有者账号 A 把虚拟交换机共享给账号 B、C、D,这样账号 A、B、C、D 都能使用该虚拟交换机,并在该交换机上挂载 ECS/RDS/SLB 等资源,以此来实现不同账号的资源共享同一个子网。

    图3.png

    图3

    相对于 CEN 和 VPN,ResourceSharing 极大地简化了配置,在网络规模较复杂的场景下尤为明显。

    基于 ResourceSharing 的网络架构

    如下图所示为基于 ResourceSharing 的网络架构:账号 1 所在的 VPC 使用两个 VSwitch 作为多可用区以实现同城双活,用于部署前端服务;再共享三个 VSwitch 给账号 2 和账号 3 用于部署后端服务:“Shared VSwitch For AZ1 ECS” 用于挂截账号 2 和账号 3 在 AZ1 的 ECS 资源,“Shared VSwitch For AZ2 ECS” 用于挂截账号 2 和账号 3 在 AZ2 的 ECS 资源,“Shared VSwitch For SLB” 用于挂截账号 2 和账号 3 的 SLB 资源;这样基于资源共享的方式,前端服务与后端服务均在同一个 VPC 下,所有服务天然支持网络互通。

    图4.png

    图4

    由于每个账号仍然独立管控自己的资源,所以服务之间既实现了多账号间网络互通,又保证了相关资源在账号间的隔离,此外,相对于 CEN 和 VPN,ResourceSharing 有一定的优势:
    • 整个体系在一个 VPC 中,因此不需要网关、路由、IPSEC、跨账号授权等相关配置,极大地简化了工作量。
    • 服务在 VPC 内部通信,不依赖公网带宽,减少了网络延时和丢包率带来的风险。
    • ResourceSharing 是完全免费的,能为用户节省不少成本。

    总结

    ResourceSharing 有众多优势,是否能取代 VPN 和 CEN 满足所有的应用场景呢?
    • 由于 VPC 是地域级别的资源,因此 ResourceSharing 是不能跨 Region 共享 VSwitch 的,而 CEN 则没有这个限制,所以跨 Region 间 VPC 通信时通常选择 CEN 的方式。如下图所示 Region A 和 Region B 的 VPC 通过 CEN 互通。
    • 当用户需要将本地数据中心与 VPC 互通以构建混合云时,ResourceSharing 显然无法满足需求。如下图所示通过 VPN 的方式将本地数据中心与安全登陆 VPC 快速连接起来。
    • 当企业对安全有更高要求时,往往需要通过 VPC 将网络划分成多个安全域,ResourceSharing 由于共享 VSwitch 所在的实例都在同一个 VPC,无法满足需求。如下图所示:根据安全级别的不同,将整个网络分隔成多个 VPC,通过 CEN 实现不同安全域 VPC 的网络互通,利用 ResourceSharing 实现多账号同安全域 VPC 的网络互通。

    图5.jpeg

    图5

    VPN 网关、CEN 和 ResourceSharing 分别适用于不同的场景,如上图所示为集团化的大型企业,网络架构较为复杂,同时用到了三种方案,用户在实际使用时,需要根据自己的需求和网络规模选择最合适的方案。

    ]]>
    来了!2020云栖大会:蚂蚁金融科技产品能力再升级-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 当前,全球正在经历一场数字化变革,给各行各业带来了前所未有的影响。尤其在金融行业,以云计算、大数据以及移动互联网等为代表的科技已经开始广泛的运用到金融服务各个领域中,传统金融行业迎来了技术全面赋能的未来金融时代,金融科技正在为金融行业的业务场景创新与技术应用创新提供助力。

    但科技与金融的融合并非一蹴而就,金融行业的数字化转型客观上还面临着如何快速搭建线上业务,如何利用互联网获客、扩大业务规模和覆盖范围,如何基于互联网用户群体的特性开发新的产品等诸多问题。

    对于传统的金融机构而言,该如何构建深度融合的技术创新体系,更好推动数字化转型?在9月17日下午13点云栖大会数字金融源动力专场分论坛中,蚂蚁金融科技将发布“三驾马车”企业级分布式关系数据库OceanBase、移动开发平台mPaaS和金融级分布式架构SOFAStack的全新能力。

    OceanBase 2.2 版本和全新一体机产品发布

    OceanBase是蚂蚁集团完全自研的企业级分布式关系数据库,作为底层的基础设施,在为金融客户持续提供安全稳定的服务的同时,能够承载上层千变万化的业务需求和各种不同的使用场景。

    据了解,OceanBase已服务大量金融、电信等行业企业,在南京银行、西安银行、天津银行、苏州银行、东莞银行、常熟农商行、广东农信、中国人保、招商证券、浙江移动等多家银行、保险、证券及运营商机构上线。

    此次云栖大会,OceanBase聚焦客户需求,将发布完整的平台能力,混合负载引擎,多种容灾方式,以及全新的OceanBase一体机。

    mPaaS:全新视频云营业厅方案发布

    作为国内领先的移动开发平台,mPaaS能够为金融服务机构提供移动开发、测试、发布、分析、智能化运营各个方面的云到端的一体化解决方案,并将在本次云栖大会中推出全新的视频云营业厅解决方案,包括视频柜员、智能远程定损理赔、金融直播、智能双录等新的业务构想。

    mPaaS已经服务了中国农业银行、广发银行,华夏银行,西安银行、国寿保险等众多B端客户,助力企业在数字时代赢得先机。例如,常熟农商银行引入mPaaS打造新一代移动金融中心,大幅提升App启动速度,有效降低闪退率和崩溃率,用户服务质量显著提升。而视频云营业厅的推出,则将金融行业更多业务场景搬到了“云上”,构建起了广域办事通道,将业务半径限制彻底“松绑”,极大改善了业务办理效率和用户体验,为企业的“全渠道数字化运营”提供了坚强后盾。

    SOFAStack:金融级应用PaaS - 高可用管理解决方案发布

    蚂蚁金融级分布式架构SOFAStack,能够提供从服务构建、应用开发、部署发布、服务治理、监控运维、容灾高可用等全生命周期、全栈式解决方案,不仅支撑着蚂蚁的丰富复杂业务,而且已全面对外开放,其中包括中国人保、南京银行、浙商证券、上海华瑞银行、四川农信银行等数十家金融机构。

    其中,作为首期国家发改委的数字化转型伙伴行动的金融机构,四川农信借助阿里金融云平台、蚂蚁金融级分布式架构SOFAStack、mPaaS技术框架、数据中台等技术支撑,推动其IT架构由集中式向分布式的架构转型。

    此次云栖大会,SOFAStack再次深入金融级PAAS运维领域,并发布金融级应用PaaS - 高可用管理解决方案,服务各类型的金融服务机构,助力其打造更好地服务大量用户的能力。

    事实上,这些产品能力升级的重磅发布,都是基于蚂蚁金融科技的定位——不断加强金融数字化基础设施的能力。“蚂蚁金融科技和阿里云共同的使命,就是成为金融行业数字化的基础设施,为金融机构的数字化转型按下‘加速按钮’”,蚂蚁集团OceanBase CEO、金融云总经理杨冰表示。

    未来,蚂蚁集团将继续坚持开放的战略依托多年沉淀下来的技术和经验,对外输出更多优质的金融科技产品和解决方案,助力金融机构建立基于云计算、数据智能、分布式业务系统等金融新基建,打造全新的金融服务体验和用户连接渠道,实现客户、场景、产品、服务等的全面数字化升级转型。

    掌握基础设施技术脉搏,搭载数字化转型发展列车,更多精彩内容,赶快“阅读原文”来云栖大会“数字金融源动力”专场参加吧~~

    640.png

    直达云栖会场:https://yunqi.aliyun.com/2020/session152


    上阿里云搜索“mPaaS”.jpg

    动态-logo.gif

    ]]>
    智能语音组件适配指南 | 《无需从0开发 1天上手智能语音离在线方案》第七章-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 智能语音终端调试指南

    1. 使用串口调试

    1.1 用内置串口命令调试

    YoC支持丰富的串口命令,通过串口命令可以完成很多调试操作。系统支持串口命令介绍如下:

    help

    > help
    help            : show commands
    ping            : ping command.
    ifconfig        : network config
    date            : date command.
    ps              : show tasks
    free            : show memory info
    sys             : sys comand
    log             : log contrtol
    iperf           : network performance test
    kv              : kv tools

    输入 help 命令,可以查看当前所有支持命令:

    image.png
    image.png
    image.png

    ps 命令可以打印出当前系统所有的线程状态,每项含义介绍如下:
    image.png

    部分信息详细说明如下:

    • 线程状态有ready、pend、suspend、sleep、deleted
    – ready:表示当前线程已经等待被调度,系统的调度原则是:若优先级不同则高优先级线程运行,优先级相同则各个线程时间片轮转运行
    – pend:表示当前线程被挂起,挂起原因是线程在等待信号量、互斥锁、消息队列等,例如调用:aos_sem_wait,aos_mutex_lock 等接口,线程就会被挂起并置成pend状态。如果是信号量等待时间是forever,则left tick 的值为 0;如果有超时时间,则 left tick 的值就是超时时间,单位为毫秒
    – suspend:表示当前线程被主动挂起,就是程序主动调用了 task_suspend 函数
    – sleep:表示当前线程被主动挂起,就是调用了 aos_sleep 等睡眠函数, left tick 的值即表示 睡眠的时间
    – deleted:当前线程已经被主动删除,也就是调用 krhino_task_del函数

    • %CPU 状态只有在 k_config.h 文件中 RHINO_CONFIG_HW_COUNT和RHINO_CONFIG_TASK_SCHED_STATS宏被设置 1 的时候才会出现。
    • 第一行 CPU USAGE: 640/10000 表示,当前系统的整体负载,如上示例,系统的CPU占有率是 0.64%

    free

    > free
                       total      used      free      peak 
    memory usage:    5652536    605316   5047220   1093576
    

    free 命令可以使用输出当前系统的堆状态,其中:

    • total 为 总的堆的大小
    • used 为 系统使用的 堆大小
    • free 为 系统空余的 堆大小
    • peak 为 系统使用的 堆最大空间

    单位为 byte

    >free mem
    
    ------------------------------- all memory blocks --------------------------------- 
    g_kmm_head = 1829bfc8
    ALL BLOCKS
    address,  stat   size     dye     caller   pre-stat    point
    0x1829cb20  used       8  fefefefe  0x0        pre-used;
    0x1829cb38  used    4128  fefefefe  0xbfffffff pre-used;
    0x1829db68  used    1216  fefefefe  0x180190b6 pre-used;
    0x1829e038  used    2240  fefefefe  0x180190b6 pre-used;
    0x1829e908  used    4288  fefefefe  0x180190b6 pre-used;
    0x1829f9d8  free     592  abababab  0x180aaa6d pre-used; free[     0x0,     0x0] 
    0x1829fc38  used      40  fefefefe  0x180cb836 pre-free [0x1829f9d8];
    0x1829fc70  used      40  fefefefe  0x180cb836 pre-used;
    0x1829fca8  used   18436  fefefefe  0x1810448d pre-used;
    0x182a44bc  used      40  fefefefe  0x180cb836 pre-used;
    ...
    0x183a5ce0  used      16  fefefefe  0x1801d477 pre-used;
    0x183a5d00  used      40  fefefefe  0x1801d477 pre-used;
    0x183a5d38  used      12  fefefefe  0x1801a911 pre-used;
    0x183a5d54  used      32  fefefefe  0x18010d40 pre-used;
    0x183a5d84  used    4288  fefefefe  0x180190b6 pre-used;
    0x183a6e54  free  4559244  abababab  0x18027fd9 pre-used; free[     0x0,     0x0] 
    0x187ffff0  used  sentinel  fefefefe  0x0        pre-free [0x183a6e54];
    
    ----------------------------- all free memory blocks ------------------------------- 
    address,  stat   size     dye     caller   pre-stat    point
    FL bitmap: 0x10f4b
    SL bitmap 0x84
    -> [0][2]
    0x18349b88  free       8  abababab  0x1802a1b1 pre-used; free[     0x0,     0x0] 
    -> [0][7]
    0x182df2f8  free      28  abababab  0x0        pre-used; free[     0x0,     0x0] 
    -> [0][25]
    
    0x182df3c8  free     100  abababab  0x18010ea5 pre-used; free[     0x0,     0x0] 
    ...
    0x182b5704  free  160204  abababab  0x1804fe55 pre-used; free[     0x0,     0x0] 
    SL bitmap 0x4
    -> [16][2]
    0x183a6e54  free  4559244  abababab  0x18027fd9 pre-used; free[     0x0,     0x0] 
    
    ------------------------- memory allocation statistic ------------------------------ 
         free     |     used     |     maxused
         5047040  |      605496  |     1093576
    
    -----------------alloc size statistic:-----------------
    [2^02] bytes:     0   |[2^03] bytes:  1350   |[2^04] bytes: 398770   |[2^05] bytes: 29121   |
    [2^06] bytes: 408344   |[2^07] bytes: 396962   |[2^08] bytes:   350   |[2^09] bytes:   231   |
    [2^10] bytes:    55   |[2^11] bytes:    38   |[2^12] bytes: 396677   |[2^13] bytes:  1410   |
    [2^14] bytes:    14   |[2^15] bytes:    16   |[2^16] bytes:     0   |[2^17] bytes:     4   |
    [2^18] bytes:    17   |[2^19] bytes:     0   |[2^20] bytes:     0   |[2^21] bytes:     0   |
    [2^22] bytes:     0   |[2^23] bytes:     0   |[2^24] bytes:     0   |[2^25] bytes:     0   |
    [2^26] bytes:     0   |[2^27] bytes:     0   |
    

    free mem 命令可以打印出堆内各个节点的细节信息 整个打印信息被分成 4个部分

    • 第一部分为 系统所有 堆节点,包含了 节点的地址、大小、占用状态、调用malloc的程序地址等
    • 第二部分为 当前系统 空置的 堆节点,信息与第一部分相同,只是单独列出了free的节点,可以观察系统的内存碎片情况
    • 第三部分为 系统的总体堆内存使用情况,和 free 命令打印出的信息相同
    • 第四部分为 堆节点的大小统计,与2的次方为单位进行划分

     >free list
                                    total      used      free      peak 
    memory usage:    5652536    605316   5047220   1093576
    
      0: caller=0xbffffffe, count= 1, total size=4128
      1: caller=0x180190b6, count=25, total size=85696
      2: caller=0x180aaa6c, count= 1, total size=592
      3: caller=0x180cb836, count= 3, total size=120
      4: caller=0x1810448c, count= 1, total size=18436
      5: caller=0x18010a68, count=39, total size=1716
      6: caller=0x18014548, count= 8, total size=580
      7: caller=0x18054dda, count= 1, total size=1028
    ...
     52: caller=0x18010d40, count= 2, total size=64
     53: caller=0x1801d5b8, count= 3, total size=72
     54: caller=0x1801d476, count= 6, total size=196
     55: caller=0x1801d5ac, count= 3, total size=48092
     56: caller=0x1801a910, count= 1, total size=12
     57: caller=0x18027fd8, count= 1, total size=4559244
    

    free list 是另一种形式的堆内存使用统计,统计了程序内各个malloc的调用并且还没有free的次数。 这个统计信息对于查找内存泄露非常有帮助。多次输出该命令,若 count 的值出现了增长,则可能有内存泄露的情况出现。

    以上命令的 caller 信息,我们可以通过 在 yoc.asm 反汇编文件查找函数来确认具体的调用函数。

    注意:free mem和free list只有在开启CONFIG_DEBUG_MM和CONFIG_DEBUG时才能使用,因为它需要占用一些内存空间用于存放这些调试信息。

    sys

    image.png

    具体显示的信息如下:

    其中 sys app 和sys id 两个命令是在需要FOTA升级的时候才会使用到,一般是OCC网站颁发的信息,不可更改,如果没有走过FOTA流程一般为空。其余的版本号信息,是代码宏定义,可以在代码中修改。

    date

    data命令是用于查询和设置当前系统时间,一般系统连上网络以后会定期调用ntp,来和服务器同步时间,这个命令可以查询同步时间和设置系统时间

    > date
        TZ(08):Tue Aug 11 18:03:14 2020 1597168994
           UTC:Tue Aug 11 10:03:14 2020 1597140194
           date -s "2001-01-01 12:13:14"
    > date -s "2020-08-11 18:15:38"
    set date to: 2020-08-11 18:15:38
        TZ(08):Wed Aug 12 02:15:38 2020 1597198538
           UTC:Tue Aug 11 18:15:38 2020 1597169738
           date -s "2001-01-01 12:13:14"

    log

    log命令可以用于控制打印等级和打印的模块

    > log
    Usage:
        set level: log level 0~5
            0:disable 1:F 2:E 3:W 4:I 5:D
        add ignore tag: log ignore tag
        clear ignore tag: log ignore
    > log level 0
    > log ignore fota
    log tag ignore list:
    fota
    > log ignore RTC
    log tag ignore list:
    fota
    RTC
    >

    log level num 用于控制打印等级
    0:关闭日志打印;
    1:打印F级别的日志;
    2:打印E级别及以上的日志;
    3:打印W级别及以上的日志;
    4:打印I级别及以上的日志;
    5:打印D级别及以上的日志,也是就日志全开

    log ignore tag 用于控制各个模块的打印
    例如log ignore RTC 表示关闭 RTC 模块的日志打印

    需要注意的是:log 命令只能控制通过 LOG 模块打印出来的日志,直接通过 printf 接口打印的日志 不能被拦截。所以推荐用 LOG 模块去打印日志。

    kv

    kv是一个小型的存储系统,通过key-value 的方式存储在flash中

    > kv
    Usage:  
        kv set key value
        kv get key
        kv setint key value
        kv getint key
        kv del key
    >

    kv set key value 是设置字符串类型的value kv setint key value 是设置整形的value

    例如:

    kv set wifi_ssid my_ssid
    kv set wifi_psk my_psk
    

    如上两条命令是用于设置wifi的 ssid和psk,重启后系统会去通过kv接口获取flash的kv value值,从而进行联网。

    ifconfig

    > ifconfig
    
    wifi0   Link encap:WiFi  HWaddr 18:bc:5a:60:d6:04
            inet addr:192.168.43.167
        GWaddr:192.168.43.1
        Mask:255.255.255.0
        DNS SERVER 0: 192.168.43.1
    
    WiFi Connected to b0:e2:35:c0:c0:ac (on wifi0)
        SSID: yocdemo
        channel: 11
        signal: -58 dBm
    

    ifconfig命令可以查看当前 网络连接的状态,其中:

    • 第一部分是 本机的网络状态,包括本机mac地址,本机IP,网关地址、掩码、DNS Server地址
    • 第二部分是 连接的路由器信息,包括wifi的名称,mac地址,连接的信道、信号质量

    1.2 创建自己的串口命令

    上一节介绍了系统内置的串口命令,本节介绍如何创建自定义串口命令用于调试。 YoC中,串口命令代码模块为cli,其代码头文件为cli.h。自定义串口命令时,需要包含这个头文件。

    代码示例如下:

    /*
     * Copyright (C) 2019-2020 Alibaba Group Holding Limited
     */
    #include <string.h>
    #include <aos/cli.h>
    
    #define HELP_INFO 
        "Usage:ntmycmd testn"
    
    static void cmd_mycmd_ctrl_func(char *wbuf, int wbuf_len, int argc, char **argv)
    {
            int i;
        
        for (i = 0; i < argc; i ++) {
            printf("argv %d: %sn", i, argv[i]);
        }
     
        printf(HELP_INFO);
    }
    
    void cli_reg_cmd_my_cmd(void)
    {
        static const struct cli_command cmd_info = {
            "my_cmd",
            "my_cmd test",
            cmd_mycmd_ctrl_func,
        };
    
        aos_cli_register_command(&cmd_info);
    }

    其中,
    • 需要定义一个被cli回调的函数,当串口输入这个命令时就会触发这个回调,本例为cmd_mycmd_ctrl_func;
    • 需要定义一个命令字符串,用于cli比较用于输入字符串来触发回调,本例为my_cmd;
    • 需要定义帮助信息,用于串口输入help命令时打印出来,本例为my_cmd test;
    • 最后在系统初始化时把这个命令注册到cli里面,本例为cli_reg_cmd_my_cmd;

    这样就可以拥有自己的串口调试命令了,效果如下:

    > my_cmd first cmd test
    argv 0: my_cmd
    argv 1: first
    argv 2: cmd
    argv 3: test
    Usage:
        mycmd test

    2. 使用GDB调试

    GDB是C/C++ 程序员的程序调试利器,很多问题使用GDB调试都可以大大提高效率。GDB在查看变量、跟踪函数跳转流程、查看内存内容、查看线程栈等方面都非常方便。

    同时,GDB也是深入理解程序运行细节最有效的方式之一,GDB 对于学习了解C语言代码、全局变量、栈、堆等内存区域的分布都有一定的帮助。

    下面我们来介绍GDB在基于玄铁内核的嵌入式芯片上的调试方法。

    2.1 建立GDB连接

    这一小节讲解一些嵌入式GDB调试使用的基础知识,和在PC上直接使用GDB调试PC上的程序会有一些区别。

    CK GDB是运行在PC上的GDB程序,通过仿真器和JTAG协议与开发板相连接,可以调试基于玄铁CPU内核的芯片。其中DebugServer为作为连接GDB和CKLink仿真器的桥梁和翻译官,一端通过网络与GDB连接,另一端通过USB线与仿真器连接。

    由于GDB与DebugServer通过网络通讯,他们可运行在同一个或不同的PC上。仿真器CKLink与开发板通过20PIN的JTAG排线连接。

    image.png

    CKLink

    CKLink 实物如下图所示。可以通过淘宝购买 。其使用方法可以查看:CKLink设备使用指南

    image.png

    DebugServer

    DebugServer有Windows 版本和Linux版本,下载和安装过程请参考:《Windows调试环境安装》,《Linux调试环境安装》。

    以Windows版本的DebugServer为例,安装完成以后,打开程序有如下界面:

    image.png

    点击连接按钮,如果连接成功会有CPU和GDB的信息打印,告知当前连接的CPU信息和开启的GDB服务信息。具体使用可以参考OCC资源下载页面下的文档:《DebugServer User Guide_v5.10》。

    2.2 启动GDB及配置

    GDB工具包含在整体的编译调试工具链里面,也可以通过OCC下载。GDB的使用都需要通过命令行完成,通过在终端敲入命令来完成交互 启动GDB通过如下命令进行:

    csky-abiv2-elf-gdb xxx.elf
    

    其中 xxx.elf 为当前板子上运行的程序,它包含了所有的程序调试信息,如果缺少elf文件则无法进行调试。

    启动GDB后输入如下命令连接DebugServer。这条命令在DebugServer的界面会有打印,可以直接复制。

    target remote [ip]:[port]
    

    需要注意的是:运行GDB程序对应的PC需要能够通过网络访问DebugServer开启的对应的IP
    连上以后就可以通过GDB 访问调试开发板上的芯片了。

    .gdbinit 文件

    .gdbinit 文件为GDB启动时默认运行的脚本文件,我们可以在.gdbinit 文件里面添加启动默认需要执行的命令,例如:target remote [ip]:[port],那么在启动GDB的时候,会直接连接DebugServer,提高调试效率。

    2.3 常用GDB命令

    这一小节介绍一些常用的GDB命令及使用方法。
    加载程序

    • 命令全名: load
    • 简化 :lo
    • 说明 :将 elf 文件 加载到 芯片中,这个命令对代码在flash运行的芯片无效。

    举例:

    (cskygdb) lo
    Loading section .text, size 0x291a00 lma 0x18600000
            section progress: 100.0%, total progress: 69.01% 
    Loading section .ram.code, size 0x228 lma 0x18891a00
            section progress: 100.0%, total progress: 69.02% 
    Loading section .gcc_except_table, size 0x8f8 lma 0x18891c28
            section progress: 100.0%, total progress: 69.08% 
    Loading section .rodata, size 0xeeac4 lma 0x18892520
            section progress: 100.0%, total progress: 94.12% 
    Loading section .FSymTab, size 0x9c lma 0x18980fe4
            section progress: 100.0%, total progress: 94.13% 
    Loading section .data, size 0x2e3c4 lma 0x18981400
            section progress: 100.0%, total progress: 98.98% 
    Loading section ._itcm_code, size 0x9b70 lma 0x189af7c4
            section progress: 100.0%, total progress: 100.00% 
    Start address 0x18600014, load size 3903412
    Transfer rate: 238 KB/sec, 4003 bytes/write.

    继续执行

    • 命令全名:continue
    • 简化 :c
    • 说明 :继续执行被调试程序,直至下一个断点或程序结束。

    举例:

    (cskygdb)c
    

    当DebugServer连接上开发板,程序会自动停止运行。等GDB挂进去以后,用c就可以继续运行程序。

    当程序在运行的时候,GDB直接挂入也会使程序停止运行,同样用c 命令可以继续运行程序。

    同样,当 load完成后,也可以使用c运行程序。

    暂停运行

    使用组件按键 ctrl + c 可以停止正在运行的程序。

    停止运行程序后就可以进行各种命令操作,如打印变量,打断点,查看栈信息,查看内存等。

    当操作完成以后,使用c 继续运行,或者使用 n/s 单步执行调试。

    打印变量

    • 命令全名: print
    • 简化 : p

    打印变量可以打印各种形式

    • 变量
    • 变量地址
    • 变量内容
    • 函数
    • 计算公式

    举例:

    (cskygdb)p g_tick_count
    (cskygdb)p &g_tick_count
    (cskygdb)p *g_tick_count
    (cskygdb)p main
    (cskygdb)p 3 * 5

    可以指定打印格式 按照特定格式打印变量

    • x 按十六进制格式显示变量。
    • d 按十进制格式显示变量。
    • o 按八进制格式显示变量。
    • t 按二进制格式显示变量。
    • c 按字符格式显示变量。

    通过这个功能,还可以进行简单的 各种进制转换

    举例:

    (cskygdb)p /x g_tick_count
    (cskygdb)p /x 1000
    (cskygdb)p /t 1000

    注意:有些局部变量会被编译器优化掉,可能无法查看。 p 命令是万能的,可以 p 变量地址,可以p 变量内容,可以p 函数地址;基本上所有符号,都可以通过p查看内容。

    设置断点

    • 命令全名: breakpoint
    • 简化 :b

    设置断电可以让程序自动停止在你希望停止的地方,断点可以以下面多种方式设置

    • 行号
    • 函数名
    • 文件名:行号
    • 汇编地址

    举例:

    (cskygdb)b 88
    (cskygdb)b main
    (cskygdb)b main.c:88
    (cskygdb)b *0x18600010

    硬件断点

    嵌入式芯片一般都有硬件断点可以设置,它相对于普通断点的不同是,该断点信息保存在cpu 调试寄存器里面,由cpu通过运行时的比较来实现断点功能,而普通断点则是通过修改该处代码的内容,替换成特定的汇编代码来实现断点功能的。 需要注意的是:硬件断点的设置会影响cpu的运行速度,但是对于一些微型的嵌入式芯片,代码放在flash这种无法写入,只能读取介质上时,就只能通过设置硬件断点才能实现断点功能,普通的断点设置将不会生效。 设置硬件断点通过另外一个命令设置,举例:

    (cskygdb)hb main
    

    设置内存断点

    • 命令全名: watchpoint
    • 简化 :watch

    设置内存断电可以在内存的内容发生变化的时候 自动停止运行。可以通过设置变量、内存断点

    举例:

    (cskygdb)watch g_tick_count
    (cskygdb)watch *0x18600010
    

    内存断点和硬件断点是相同的原理,只要是cpu运行导致的内存修改都会自动停止运行。内存断点和硬件断点都会都会占用cpu的调试断点数,每个芯片都由固定有限的个数可供设置,一般为4个或者8个等。

    查看断点

    • 命令全名:info breakpoint
    • 简化 :i b

    举例:

    (cskygdb) i b
    Num     Type           Disp Enb Address    What
    1       breakpoint     keep y   0x18704f9c in main 
                                               at vendor/tg6100n/aos/aos.c:110
    2       breakpoint     keep y   0x1871ca9c in cpu_pwr_node_init_static 
                                               at kernel/kernel/pwrmgmt/cpu_pwr_hal_lib.c:88

    使能断点

    • 命令全名:enable
    • 简化 :en

    举例:

    (cskygdb)en 1
    

    禁止断点

    • 命令全名:disable
    • 简化 :dis

    举例:

    (cskygdb)dis 1
    

    查看栈信息

    • 命令全名: backtrace
    • 简化 : bt

    例如:

    (cskygdb) bt
    #0  board_cpu_c_state_set (cpuCState=1, master=1)
        at vendor/tg6100n/board/pwrmgmt_hal/board_cpu_pwr.c:103
    #1  0x1871cb98 in cpu_pwr_c_state_set_ (
        all_cores_need_sync=<optimized out>, master=<optimized out>, 
        cpu_c_state=CPU_CSTATE_C1, 
        p_cpu_node=0x189d2100 <cpu_pwr_node_core_0>)
        at kernel/kernel/pwrmgmt/cpu_pwr_hal_lib.c:275
    #2  _cpu_pwr_c_state_set (target_c_state=CPU_CSTATE_C1)
        at kernel/kernel/pwrmgmt/cpu_pwr_hal_lib.c:495
    #3  cpu_pwr_c_state_set (target_c_state=CPU_CSTATE_C1)
        at kernel/kernel/pwrmgmt/cpu_pwr_hal_lib.c:524
    #4  0x1871d20c in tickless_enter ()
        at kernel/kernel/pwrmgmt/cpu_tickless.c:381
    #5  0x1871ce74 in cpu_pwr_down ()
        at kernel/kernel/pwrmgmt/cpu_pwr_lib.c:70
    #6  0x187095a4 in idle_task (arg=<optimized out>)
        at kernel/kernel/rhino/k_idle.c:48
    #7  0x1870bf44 in krhino_task_info_get (task=<optimized out>, 
        idx=<optimized out>, info=0x8000000)
        at kernel/kernel/rhino/k_task.c:1081
    Backtrace stopped: frame did not save the PC
    

    选择栈帧

    • 命令全名: frame
    • 简化 :f

    举例:

    (cskygdb) f 2
    #2  _cpu_pwr_c_state_set (target_c_state=CPU_CSTATE_C1)
        at kernel/kernel/pwrmgmt/cpu_pwr_hal_lib.c:495
    495                 ret = cpu_pwr_c_state_set_(p_cpu_node, target_c_state, master, FALSE);

    选择了栈帧就可以通过 p 命令查看该栈函数内的局部变量了。(函数内的局部变量是存放在栈空间中的)

    单步执行

    • 命令全名: next
    • 简化 :n

    举例:

    (cskygdb) n
    

    单步执行进入函数

    • 命令全名: step
    • 简化 :s

    举例:

    (cskygdb) s
    

    单步执行(汇编)

    • 命令全名: nexti
    • 简化 :ni

    举例:

    (cskygdb) ni
    

    单步执行进入函数(汇编)

    • 命令全名: stepi
    • 简化 :si

    举例:

    (cskygdb) si
    

    相对于s 的单步执行,si的单步执行精确到了汇编级别,每一个命令执行一条汇编指令。对于优化比较严重的函数,s 的按行 单步执行 流程往往会比较混乱,按汇编的单步执行则会比较符合芯片底层的逻辑。当然使用si单步调试程序,也需要程序员对于汇编指令有比较好的了解,调试难度也比较大。但是对于嵌入式程序,编译器必然会对程序进行各种优化,s 的单步调试往往不是很好的选择。

    完成当前函数

    • 命令全名: finish
    • 简化 :fin

    举例:

    (cskygdb) fin
    

    当想跳出该函数调试时,使用该命令会相当方便。但是该命令有一个限制,当在不会支持普通断点的设备上调试时(代码放在flash上执行),这个命令需要配合 另一条命令才能生效

    (cskygdb) set debug-in-rom
    

    这条命令的意思是,告诉gdb这个代码是放在flash上的,需要使用硬件断点才能使用fin命令,这条命令只需要执行一次。

    设置变量

    • 命令格式:

    set [variable] = [value]
    

    举例:

    (cskygdb) set g_tick_count = 100
    (cskygdb) set *0x186000010 = 0x10
    

    在调试一些程序逻辑时,通过设置变量数值可以让程序走期望的流程,来方便调试。

    查看内存

    • 命令格式

    x /[n][f][u] [address]
    

    其中:
    • n 表示显示内存长度,默认值为1
    • f 表示显示格式,如同上面打印变量定义
    • u 表示每次读取的字节数,默认是4bytes
    – b 表示单字节
    – h 表示双字节
    – w 表示四字节
    – g 表示八字节

    举例:

    (cskygdb) x /20x 0x18950000
    0x18950000:     0x6f445f6c      0x72652077      0x21726f72      0x6c43000a
    0x18950010:     0x546b636f      0x72656d69      0x5f6c633a      0x61746164
    0x18950020:     0x6c633e2d      0x6365535f      0x74696220      0x2070616d
    0x18950030:     0x61207369      0x30206c6c      0x21212120      0x6c43000a
    0x18950040:     0x546b636f      0x72656d69      0x5f6c633a      0x61746164

    这条命令对于调试踩内存,栈溢出等大量内存变化的场景非常有帮助。

    2.4 快速上手调试

    接下来,你可以找一块开发板,按照下面步骤体验GDB调试过程:

    • 如前面介绍,下载并安装DebugServer
    • GDB 连上DebugServer
    • lo //灌入编译好的 elf
    • b main //打断点到 main函数入口
    • c //运行程序
    • 如果顺利,这时程序应该自动停在main函数入口
    • n //单步执行下一行程序,可以多执行几次
    • 找几个全局变量, p 查看结果

    大部分开发板上电都自动会运行程序,连上DegbuServer就会停止运行。

    注意事项

    • 调试的时候 elf 文件 一定要和运行程序对应上,不然没法调试,使用一个错误的elf文件调试程序,会出现各种乱七八糟的现象。而且同一份代码,不同的编译器,不同的主机编译出来的elf都可能不相同。所以保存好编译出来的elf相当重要
    • 对于一些代码运行在 flash的芯片方案,GDB调试的时候要注意转换,和在ram上GDB调试命令有一些不一样。
    • watch 只能观察到CPU的内存更改行为,如果是外设(DMA等)运行导致的内存变化,不能被watch到
    • CKLink 连接开发板可能存在各种问题连接不上,要仔细检查,包括:开发板是否上电,芯片是否上电,芯片是否在运行,JTAG排线是否插反等等。

    3. CPU异常分析及调试

    3.1 CPU异常案例

    在开发板运行过程中,有时会突然出现如下打印,进而程序停止运行,开发板也没有任何响应:

    CPU Exception: NO.2
    r0: 0x00000014  r1: 0x18a70124  r2: 0x00001111  r3: 0x10020000  
    r4: 0x00000000  r5: 0x00000001  r6: 0x00000002  r7: 0x07070707  
    r8: 0x00000000  r9: 0x09090909  r10: 0x10101010 r11: 0x11111111 
    r12: 0x40000000 r13: 0x00000000 r14: 0x18b166a8 r15: 0x186d9c0a 
    r16: 0x16161616 r17: 0x47000000 r18: 0x3f800000 r19: 0x00000000 
    r20: 0xc0000000 r21: 0x40000000 r22: 0x00000000 r23: 0x00000000 
    r24: 0x40400000 r25: 0x12345678 r26: 0x12345678 r27: 0x12345678 
    r28: 0x12345678 r29: 0x12345678 r30: 0x12345678 r31: 0x12345678 
    vr0: 0x12345678 vr1: 0x00000000 vr2: 0x00000000 vr3: 0x00000000 
    vr4: 0x00000000 vr5: 0x00000000 vr6: 0x00000000 vr7: 0x00000000 
    vr8: 0x00000000 vr9: 0x00000000 vr10: 0x00000000    vr11: 0x00000000    
    vr12: 0x00000000    vr13: 0x00000000    vr14: 0x00000000    vr15: 0x00000000    
    vr16: 0x00000000    vr17: 0x00000000    vr18: 0x00000000    vr19: 0x00000000    
    vr20: 0x00000000    vr21: 0x00000000    vr22: 0x00000000    vr23: 0x00000000    
    vr24: 0x00000000    vr25: 0x00000000    vr26: 0x00000000    vr27: 0x00000000    
    vr28: 0x00000000    vr29: 0x00000000    vr30: 0x00000000    vr31: 0x00000000    
    vr32: 0x00000000    vr33: 0x00000000    vr34: 0x00000000    vr35: 0x00000000    
    vr36: 0x00000000    vr37: 0x00000000    vr38: 0x00000000    vr39: 0x00000000    
    vr40: 0x00000000    vr41: 0x00000000    vr42: 0x00000000    vr43: 0x00000000    
    vr44: 0x00000000    vr45: 0x00000000    vr46: 0x00000000    vr47: 0x00000000    
    vr48: 0x00000000    vr49: 0x00000000    vr50: 0x00000000    vr51: 0x00000000    
    vr52: 0x00000000    vr53: 0x00000000    vr54: 0x00000000    vr55: 0x00000000    
    vr56: 0x00000000    vr57: 0x00000000    vr58: 0x00000000    vr59: 0x00000000    
    vr60: 0x00000000    vr61: 0x00000000    vr62: 0x00000000    vr63: 0x00000000    
    
    epsr: 0xe4000341
    epc : 0x186d9c12

    这段打印表明程序已经崩溃。接下来以它为例,来一步一步分析如何调试和解决。

    3.2 基础知识介绍

    3.2.1 关键寄存器说明

    • pc:程序计数器,它是一个地址指针,指向了程序执行到的位置
    • sp:栈指针,它是一个地址指针,指向了当前任务的栈顶部,它的下面存了这个任务的函数调用顺序和这些被调用函数里面的局部变量。在玄铁CPU框架里,它对应了 R14 寄存器
    • lr:连接寄存器,它也是一个地址指针,指向子程序返回地址,也就是说当前程序执行返回后,执行的第一个指令就是lr寄存器指向的指令,在玄铁CPU框架里,它对对应了 R15 寄存器
    • epc:异常保留程序计数器,它是一个地址指针,指向了异常时的程序位置,这个寄存器比较重要,出现异常后,我们就需要通过这个寄存器来恢复出现异常时候的程序位置。
    • epsr:异常保留处理器状态寄存器,它是一个状态寄存器,保存了出异常前的系统状态。
    这几个重要的寄存器都在上面的异常打印中打印出来了。

    3.2.2 关键文件说明

    • yoc.elf:保存了程序的所有调试信息,GDB调试时必须用到该文件,编译完程序后务必保留该文件。
    • yoc.map:保存了程序全局变量,静态变量,代码的存放位置及大小。
    • yoc.asm:反汇编文件,保存了程序的所有反汇编信息。这些文件都保存在每个solutions目录中。如果使用CDK开发,则位于项目的Obj目录中。
    其中:
    • yoc.map 文件必须在编译链接的时候通过编译选项生成,例如:CK的工具链的编译选项为-Wl,-ckmap='yoc.map'
    • yoc.asm 文件可以通过elf 文件生成,具体命令为csky-abiv2-objdump -d yoc.elf > yoc.asm

    3.2.3 异常号说明

    在XT CPU架构里,不同的cpu异常会有不同的异常号,我们往往需要通过异常号来判断可能出现的问题。

    image.png
    image.png

    这些异常中,出现最多的是 1、2 号异常,4、7 偶尔也会被触发,3号异常比较好确认。

    3.3 异常分析过程

    GDB准备及连接
    参考上节:《2. 使用GDB调试》。

    恢复现场

    在GDB 使用 set 命令 将异常的现场的通用寄存器和 PC 寄存器设置回CPU中,便可以看到崩溃异常的程序位置了

    (cskygdb)set $r0=0x00000014
    (cskygdb)set $r1=0x18a70124
    (cskygdb)set $r2=0x00001111
    (cskygdb)set $r3=0x10020000 
    ...
    (cskygdb)set $r14=0x18b166a8
    (cskygdb)set $r15=0x186d9c0a
    ...
    (cskygdb)set $r30=0x12345678
    (cskygdb)set $r31=0x12345678
    (cskygdb)set $pc=$epc

    不同的CPU 通用寄存器的个数有可能不相同,一般有 16个通用寄存器、32个通用寄存器两种版本,我们只需要把通用寄存器,即 r 开头的寄存器,设置回CPU即可。 pc,r14,r15 三个寄存器是找回现场的关键寄存器,其中r14,r15分别是 sp 寄存器和 lr寄存器,pc寄存器需要设置成epc。其余的通用寄存器是一些函数传参和函数内的局部变量。

    设置完成以后,通过 bt命令可以查看异常现场的栈:

    (cskygdb) bt
    #0  0x186d9c12 in board_yoc_init () at vendor/tg6100n/board/init.c:202
    #1  0x186d9684 in sys_init_func () at vendor/tg6100n/aos/aos.c:102
    #2  0x186dfc14 in krhino_task_info_get (task=<optimized out>, idx=<optimized out>, info=0x11)
        at kernel/kernel/rhino/k_task.c:1081
    Backtrace stopped: frame did not save the PC
    
    
    
    从 bt 命令打印出来的栈信息,我们可以看到 异常点在 init.c 的 202 行上,位于board_yoc_init函数内。 到这里,对于一些比较简单的错误,基本能判断出了什么问题。 如果没法一眼看出问题点,那我们就需要通过异常号来对应找BUG了。

    3.4 通过异常号找BUG

    程序崩溃后,异常打印的第一行就是CPU异常号。

    CPU Exception: NO.2
    

    如上,我们示例中的打印是2号异常。 2号异常是最为常见的异常,1号异常也较为常见。4号、7号一般是程序跑飞了,运行到了一个不是程序段的地方。3号异常就是除法除零了,比较好确认。其余的异常基本不会出现,出现了大概率也是芯片问题或者某个驱动问题,不是应用程序问题。

    CPU Exception: NO.1

    一号异常是访问未对齐异常,一般是一个多字节的变量从一个没有对齐的地址赋值或者被赋值。 例如:

    uint32_t temp;
    uint8_t data[12];
    temp = *((uint32_t*)&data[1]);

    如上代码,一个 4字节的变量 temp从 一个单字节的数组中取4个字节内容,这种代码就容易出现地址未对齐异常。这种操作在一些流数据的拆包组包过程比较常见,这个时候就需要谨慎小心了。

    有些CPU 可以开启不对齐访问设置,让CPU可以支持从不对齐的地址去取多字节,这样就不会出现一号异常。但是为了平台兼容性,我们还是尽量不要出现这样的代码。

    CPU Exception: NO.2

    二号异常是访问错误异常,一般是访问了一个不存在的地址空间。 例如:

    uint32_t *temp;
    *temp = 1;
    

    如上代码,temp指针未初始化,如果直接给 temp指针指向的地址赋值,有可能导致二号异常,因为temp指向的地址是个随机值,该地址可能并不存在,或者不可以被写入。 二号异常也是最经常出现的异常,例如常见的错误有:

    • 内存访问越界
    • 线程栈溢出
    • 野指针赋值
    • 重复释放指针(free)

    请注意你代码里的 memset、memcpy、malloc、free 、strcpy等调用。

    大部分2号异常和1号异常的问题,异常的时候都不是第一现场了,也就是说异常点之前就已经出问题了。

    比如之前就出现了 memcpy的 内存访问越界,内存拷贝超出变量区域了。memcpy的时候是不会异常的,只有当程序使用了这些被memcpy 踩了内存时,才会出现一号或二号异常。

    这个时候异常点已经不是那个坑的地方了,属于“前人埋坑,后人遭殃”型问题。

    如果是一些很快就复现的问题,我们可以通过GDB watch命令,watch那些被踩的内存或变量来快速的定位是哪段代码踩了内存。

    如果是一些压测出现的问题,压测了2天,出了一个2号异常,恭喜你,碰到大坑了。类似这种,比较难复现的问题,watch已经不现实了。

    结合异常现场GDB查看变量、内存信息和review代码逻辑,倒推出内存踩踏点,是比较正确的途径。

    再有,就是在可疑的代码中加 log日志,增加压测的机器,构造缩短复现时间的case等一些技巧来加快BUG解决的速度。

    CPU Exception: NO.4/NO.7

    四号异常是指令非法,即这个地址上的内容并不是一条CPU机器指令,不能被执行。 七号异常是断点异常,也就是这个指令是断点指令,即 bktp 指令,这是调试指令,一般代码不会编译生成这种指令。 这两种异常大概率是 指针函数没有赋值就直接跳转了,或者是代码段被踩了

    例如:

    typedef void (*func_t)(void *argv);
    
    func_t f;
    void *priv = NULL;
    
    if (f != NULL) {
        f(priv);
    }

    如上代码,f是一个 函数指针,没有被赋值,是一个随机值。直接进行跳转,程序就肯定跑飞了。 这种异常,一般epc地址,都不在反汇编文件 yoc.asm 中。

    CPU Exception: NO.3

    3号异常是除零异常,也是最简单、最直接的一种异常。 例如:

    int a = 100;
    int b = 0;
    
    int c = a / b; 

    如上代码,b 变量位 0,除零就会出现 三号异常。

    3.5 不用GDB找到异常点

    有些时候无法使用GDB去查看异常点,或者搭环境不是很方便怎么办? 这个时候我们可以通过反汇编文件和epc地址来查看产生异常的函数。 打开yoc.asm 反汇编文件,在文件内搜索epc地址,就可以找到对应的函数,只是找不到对应的行号。

    例如:

    186d9b14 <board_yoc_init>:
    186d9b14:   14d3        push        r4-r6, r15
    186d9b16:   1430        subi        r14, r14, 64
    186d9b18:   e3ffffc6    bsr         0x186d9aa4  // 186d9aa4 <speaker_init>
    186d9b1c:   3001        movi        r0, 1
    186d9b1e:   e3fe3221    bsr         0x1869ff60  // 1869ff60 <av_ao_diff_enable>
    186d9b22:   e3fe4ca9    bsr         0x186a3474  // 186a3474 <booab_init>
    186d9b26:   e3fffe7d    bsr         0x186d9820  // 186d9820 <firmware_init>
    ...
    186d9bfc:   1010        lrw         r0, 0x188d1a50  // 186d9c3c <board_yoc_init+0x128>
    186d9bfe:   e00c6aeb    bsr         0x188671d4  // 188671d4 <printf>
    186d9c02:   ea231002    movih       r3, 4098
    186d9c06:   ea021111    movi        r2, 4369
    186d9c0a:   b340        st.w        r2, (r3, 0x0)
    186d9c0c:   1410        addi        r14, r14, 64
    186d9c0e:   1493        pop         r4-r6, r15
    186d9c12:   9821        ld.w        r1, (r14, 0x4)
    186d9c14:   07a4        br          0x186d9b5a  // 186d9b5a <board_yoc_init+0x46>
    186d9c14:   188d19c0    .long   0x188d19c0

    如上的汇编代码,根据异常的epc地址0x186d9c12,我们可以确认异常发生在board_yoc_init函数内。

    ]]>
    浅谈基于 Git 的版本控制工作流-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    博主说:本文借鉴了很多「 DRPrincess」博主的文章内容,在此对其表示感谢。

    为了更好的理解基于 Git 的版本控制工作流,我们不妨先来回答几个问题?

    • 什么是版本控制?
    • 什么是版本控制系统?
    • 为什么要做版本控制?
    • 为什么选择基于 Git 的版本控制?

    要回答这些问题,最好的方法,莫过于回顾一下版本控制的发展历史。

    因此,在本文中,我们就从「[版本控制简史」出发,揭开「基于 Git 的版本控制工作流」的神秘面纱。

    版本控制简史

    版本控制,是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理。版本控制最主要的目的就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变,文件的版本号都将增加。

    除了记录版本变更外,版本控制的另一个重要功能是并行开发。软件开发往往是多人协同作业,版本控制可以有效地解决版本的同步以及不同开发者之间的开发通信问题,提高协同开发的效率。并行开发中最常见的不同版本软件的错误修正问题也可以通过版本控制中分支与合并的方法有效地解决。

    但版本控制是目的而不是实现的工具,所以我们还需要通过某种工具来实现版本控制的目的,我们将这样的工具称之为 Version Controll System,缩写为 VCS,即版本控制系统。我们可以把一个版本控制系统简单的理解为一个“数据库”,在需要的时候,它可以帮我们完整地保存一个项目的快照。当我们需要查看一个之前的快照(称之为“版本”)时,版本控制系统可以显示出当前版本与上一个版本之间的所有改动的细节。

    早在 1986 年 12 月,Dick Grune 就以 shell 脚本的形式发布了第一个流行的版本控制系统 CVS 的雏形。1989 年 4 月,Brian Berliner 设计了 CVS 并编写了代码。CVS 是一个 C/S 系统,其设计思路为,在一台服务器上建立一个源代码库,库里可以存放许多不同项目的源程序,由源代码库管理员统一管理这些源程序。每个用户在使用源代码库之前,首先要把源代码库里的项目文件下载到本地,然后用户可以在本地任意修改,最后用 CVS 命令进行提交,由 CVS 源代码库统一管理修改。这样,就好像只有一个人在修改文件一样,既避免了冲突,又可以做到跟踪文件变化等。

    2000 年,CollabNet Inc 开发了 Subversion,缩写为 SVN,是一个开放源代码的版本控制系统,现已发展成了 Apache 基金会的项目。相对于 CVS,SVN 采用了分支管理系统,它的设计目标就是取代 CVS,但与 CVS 相同的是,SVN 也采用了 C/S 体系,项目的各种版本都存储在服务器上,程序开发人员首先将从服务器上获得一份项目的最新版本,并将其复制到本机,然后在此基础上,每个开发人员可以在自己的客户端进行独立的开发工作,并且可以随时将新代码提交给服务器。当然也可以通过更新操作获取服务器上的最新代码,从而保持与其他开发者所使用版本的一致性。

    2005 年,Linux 之父 Linus Torvalds 为了帮助管理 Linux 内核开发而开发了一个开放源码的版本控制软件 Git。说起来,Git 的诞生还有一些戏剧性,Linus 最初使用 BitKeeper 作为版本控制系统,但在 2005 年,Andrew Tridgell 写了一个程序,可以连接 BitKeeper 的存储库,BitKeeper 著作权拥有者 Larry McVoy 认为 Andrew Tridgell 对 BitKeeper 内部使用的协议进行逆向工程,决定收回无偿使用 BitKeeper 的许可。Linux 内核开发团队与 BitMover 公司进行磋商,但无法解决他们之间的歧见。最终,Linus Torvalds 决定自行开发版本控制系统替代 BitKeeper,就用十天的时间编写出了 Git 的第一个版本。

    如上所述,从 CVS、到 SVN、再到 Git 的变化,也是版本控制系统演进的过程。我们可以将 CVS、SVN 和 Git 大致分为两类:

    • 集中式版本控制系统:CVS 和 SVN 属于这一类。它们用集中管理的单一服务器,来保存所有文件修订版本,而协同工作的人们都通过客户端连到这台服务器,下载最新的代码或者是更新提交。但是如果中央服务器宕机了,那宕机的这一段时间,大家都无法更新提交更新,没法协同工作;更糟糕的情况下,如果中央服务器的数据没有做备份而且损坏,那么所有记录就都丢失了。
    • 分布式版本控制系统:Git 属于这一类。分布式版本控制系统最大的特点就是客户端并不只是提取最新版本的文件快照,而是把代码仓库完整地镜像下来,每个客户端其实都可以当做是中央服务器,当中央服务器数据损坏了,从任何一个本地客户端都可以重新恢复。而且我们可以随时随地提交代码,因为我们提交代码是提交到本地的服务器,所以效率大大提高。

    现如今,Git 应该算是最受欢迎的版本控制工具了。例如现在世界上最大的两个代码托管平台 GitHub 和 GitLab,都是基于 Git 进行版本控制的;在国内,大家使用较多的中文代码托管平台 Gitee,也是基于 Git 进行版本控制的。由此可见,Git 作为版本控制工具,其速度快、分布式等特性,深受大家喜爱的。因此,了解基于 Git 的版本控制工作流,还是与我们有益的!

    什么是工作流?

    工作流,即工作流程。在项目开发过程中,多人协作是很常见的现象,每个人拉取自己分支、实现自己的业务逻辑,虽然各自在分支上互不干扰,但是我们总归需要把分支合并到一起,而且真实项目中涉及到很多问题,例如版本迭代,版本发布,bug 修复等,为了更好的管理代码,需要制定一个工作流程,这就是我们说的工作流,也有人叫它分支管理策略。

    工作流不涉及任何命令,因为它就是一个规则,完全由开发者自定义,并且自行遵守,正所谓无规矩不成方圆,就是这个道理。其中,Git Flow 出现的最早,GitHub Flow 在 Git Flow 的基础上,做了一些优化,适用于持续版本的发布,而 GitLab Flow 出现的时间比较晚,所以综合了前面两种工作流的优点,制定而成的一种工作流。接下来,我们就详细了解这三个工作流。

    Git Flow

    Git Flow 是 Vincent Driessen 2010 年发布出来的他自己的分支管理模型,到现在为止,使用度非常高,可以说是一个非常成熟的 Git 工作流。Git Flow 的分支结构,按功能来说,可以分为 5 种分支,从 5 种分支的生命周期上,又可以分为长期分支和短期分支,或者更贴切的描述为,主要分支和辅助分支。

    主要分支

    在采用 Git Flow 工作流的项目中,代码的中央仓库会一直存在以下两个长期分支:

    • master
    • develop

    其中,origin/master分支上的最新代码永远是版本发布状态,origin/develop分支则是最新的开发进度。当develop上的代码达到一个稳定的状态,可以发布版本的时候,develop上这些修改会以某种特别方式被合并到master分支上,然后标记上对应的版本标签。

    辅助分支

    除了主要分支,Git Flow 的开发模式还需要一系列的辅助分支,来帮助更好的并行开发,简化功能开发和问题修复。辅助分支不需要一直存在,仅当我们需要的时候,创建辅助分支就可以,当我们不需要的时候,也可以删除辅助分支。辅助分支分为以下几类:

    • Feature Branch
    • Release Branch
    • Hotfix Branch

    Feature 分支用来做分模块功能开发,命名看开发者喜好,不要和其他类型的分支命名弄混淆就好,举个坏例子,命名为master就是一个非常不妥当的举动。模块完成之后,会合并到develop分支,然后删除自己。

    Release 分支用来做版本发布的预发布分支,建议命名为release-xxx。例如在软件1.0.0版本的功能全部开发完成,提交测试之后,从develop检出release-1.0.0,测试中出现的小问题,在release分支进行修改提交,测试完毕准备发布的时候,代码会合并到masterdevelopmaster分支合并后会打上对应版本标签v1.0.0,合并后删除自己,这样做的好处是,在测试的时候,不影响下一个版本功能并行开发。

    Hotfix 分支是用来做线上的紧急 bug 修复的分支,建议命名为hotfix-xxx。当线上某个版本出现了问题,将检出对应版本的代码,创建 Hotfix 分支,问题修复后,合并回masterdevelop,然后删除自己。这里注意,合并到master的时候,也要打上修复后的版本标签。

    Merge 加上 --no-ff 参数

    需要说明的是,Git Flow 的作者 Vincent Driessen 非常建议,合并分支的时候,加上--no-ff参数,这个参数的意思是不要选择 Fast-Forward 合并方式,而是策略合并,策略合并会让我们多一个合并提交。这样做的好处是保证一个非常清晰的提交历史,可以看到被合并分支的存在。下面是对比图,左侧是加上参数的,后者是普通的提交:

    git-merge

    示意图

    git-flow

    如上图所示,这是 Vincent Driessen 于 2010 年给出的 Git Flow 示意图,也是我们所有想要学习 Git Flow 的人都应该了解的一张图。图中画了 Git Flow 的五种分支,masterdevelopfeaturereleasehoxfixes,其中masterdevelop字体被加粗代表主要分支。master分支每合并一个分支,无论是hotfix还是release,都会打一个版本标签。通过箭头可以清楚的看到分支的开始和结束走向,例如feature分支从develop开始,最终合并回develophoxfixesmaster检出创建,最后合并回developmastermaster也打上了标签。

    GitHub Flow

    GitHub Flow 是世界上最大的代码托管平台,也称为“世界上最大的同性交友网站” GitHub 制定并使用的工作流,其是一个轻量级,基于分支的工作流,支持团队和项目的定期部署,由 Scott Chacon 在 2011 年 8月 31 号正式发布。

    模型说明

    在 GitHub Flow 中,只有一个长期分支master,而且master分支上的代码永远是可发布状态。一般来说,master会设置为受保护状态,只有有权限的人才能推送代码到master分支。以 GitHub 官方教程为准,遵循 GitHub Flow 需要经历以下几个步骤:

    • 创建分支
    • 添加提交
    • 提出 PR 请求
    • 讨论和评估你的代码
    • 部署
    • 合并

    简单解释一下,其大致流程为:如果有新功能开发、版本迭代或者 bug 修复等需求,我们就从master分支上检出新的分支;将检出的新分支代码拉取到本地,在本地环境中进行编码,完成后,向远程新分支仓库推送代码;当我们需要反馈问题、取得帮助,或者想合并分支代码时,可以发起一个 Pull Request,常简称为 PR;当我们的代码通过项目维护者(有权限向master分支合并代码的人)讨论和评估后,就可以部署代码;待部署完成、验证通过后,代码就应该被合并到目标分支。

    示意图

    github-flow

    与 Git Flow 的示意图相比,GitHub Flow 的示意图可以称得上简单明了,因为 GitHub Flow 推荐做法就是只有一个主分支master,团队成员们的分支代码通过 PR 来合并到主分支上。实际上,上面的图仅是创建分支的示意图,但无论是创建分支还是添加提交、提出 PR 请求等,都不过是围绕着主分支按照上述的流程推进而已,如果大家感兴趣,可以通过「 深入理解 GitHub Flow」查看全部示意图。

    特色功能

    因为 GItHub Flow 的初衷就是用于在 GitHub 上进行团队协作,所以借助于 GitHub 平台的功能,GItHub Flow 中也引入了一些比较实用的工作流程,其中最出色的两个功能莫过于 PR 与问题追踪了。

    PR

    在工作流中引入 PR,是 GItHub Flow 的一个特色,它的用处并不仅仅是合并分支,还有以下功能:

    • 控制分支合并权限
    • 问题讨论或者寻求其他小伙伴们的帮助
    • Code Review

    有了 PR 功能之后,相信我们再提交代码的时候,就得慎之又慎了。否则的话,代码写的太烂,就等着被喷吧!

    问题追踪

    在日常开发中,我们可能会用到很多第三方的开源库,如果使用过程中遇到了问题,我们可以去其 GitHub 仓库上搜索一下 Issue 列表,看看有没有人遇到过、项目维护者修复了没有,一般未解决的 Issue 是Open状态,已解决的 Issue 是Closed状态,这就是问题追踪。

    如果你是一个项目维护者,除了标记 Issue 的开启和关闭,还可以给它标记上不同的标签。当提交的时候,如果提交信息中有fix #1等字段,可以自动关闭对应编号的 Issue。

    GitLab Flow

    这个工作流十分地年轻,是 GitLab 的 CEO Sytse Sijbrandij 在 2014 年 9月 29 正式发布出来的。因为出现的比前面两种工作流稍微晚一些,所以它有个非常大的优势,集百家之长,补百家之短。GitLab 既支持 Git Flow 的分支策略,也支持 GitHub Flow 的 PR 和问题追踪。

    Git Flow & GitHub Flow 的瑕疵

    当 Git Flow 出现后,它解决了之前项目管理的很让人头疼的分支管理,但是实际使用过程中,也暴露了很多问题:

    • 默认工作分支是develop,但是大部分版本管理工具默认分支都是master,开始的时候总是需要切换很麻烦。
    • Hotfix 和 Release 分支在需要版本快速迭代的项目中,几乎用不到,因为刚开发完就直接合并到master发版,出现问题develop就直接修复发布下个版本了。
    • Hotfix 和 Release 分支,一个从master创建,一个从develop创建,使用完毕,需要合并回developmaster。而且在实际项目管理中,很多开发者会忘记合并回develop或者master

    GitHub Flow 的出现,非常大程度上简化了 Git Flow ,因为只有一个长期分支master,并且提供 GUI 操作工具,一定程度上避免了上述的几个问题,然而在一些实际问题面前,仅仅使用master分支显然有点力不从心,例如:

    • 版本的延迟发布(例如 iOS 应用审核到通过中间,可能也要在master上推送代码)
    • 不同环境的部署 (例如:测试环境,预发环境,正式环境)
    • 不同版本发布与修复 (是的,只有一个master分支真的不够用)

    GitLab Flow 解决方案

    为了解决上面提到的那些问题,GitLab Flow 给出了以下的解决方法。

    版本的延迟发布 Prodution Branch

    master分支不够,于是添加了一个prodution分支,专门用来发布版本。

    master-production

    不同环境的部署 Environment Branches & Upstream First

    每个环境,都对应一个分支,例如下图中的pre-productionprodution分支都对应不同的环境,这个工作流模型比较适用服务端,测试环境,预发环境,正式环境,一个环境建一个分支。

    这里要注意,代码合并的顺序,要按环境依次推送,确保代码被充分测试过,才会从上游分支合并到下游分支。除非是很紧急的情况,才允许跳过上游分支,直接合并到下游分支。这个被定义为一个规则,名字叫 “upstream first”,翻译过来是 “上游优先”。

    master-pre-production

    版本发布分支 Release Branches & Upstream First

    只有当对外发布软件的时候,才需要创建release分支。对外发布版本的记录是非常重要的,如果线上出现了一个问题,需要拿到问题出现对应版本的代码,才能准确定位问题。

    在 Git Flow 中,版本记录是通过master上的tag来记录的。发现问题,创建hotfix分支,完成之后合并到masterdevelop

    在 GitLab Flow 中,建议的做法是每一个稳定版本,都要从master分支拉出一个分支,比如2-3-stable2-4-stable等等。发现问题,就从对应版本分支创建修复分支,完成之后,先合并到master,然后才能再合并到release分支,遵循 “上游优先” 原则。

    master-stable

    分支命名实践

    现如今,越来越多的公司都会利用 GitLab 来搭建自己的代码托管平台,因此就以 GitLab Flow 为例,给出一个较好的分支命名实践。

    如果存在多个环境,则为每个环境建立一个长期分支,可以命名为:

    • master,表示主分支,用于生产环境;
    • beta,表示内测分支,用于内测环境;
    • test,表示测试分支,用于测试环境。

    在此,着重解释一下“内测环境”吧,实际上,内测环境应该是生产环境的一部分,是从生产环境隔离出来一部分用于内测,以保证线上回归测试时不影响真实的用户,因此两者共用一套生产数据库,仅是通过流量入口做区分。

    接下来,根据不同的目的,为新拉取的分支取不同的名称:

    • 如果是开发需求,则从master拉取新分支,命名为feature-1xx-2xx-3xx,其中每一部分都有不同的含义,如

      • feature为固定词,表示这是一个新特性分支;
      • 1xx表示新特性的描述,为防止分支名过长,可以用缩写;
      • 2xx表示新分支创建的时间,格式为YYYYMMDD
      • 3xx表示新分支的创建者,姓名拼音或者英文名均可。

    给出一个开发需求的分支命名示例,feature-SupportIM-20200711-chariesgavin,整个分支名称的含义就是,“某人在某时创建了某个功能的新特性分支”。开发、测试及代码合并的流程,大致如下:

    1. master分支拉取新的开发分支,进行编码,自测;
    2. 自测完成后,将代码合并到test分支,并且在test环境进行测试;
    3. test环境测试通过后,将代码合并到beta分支,并且在beta环境进行线上回归测试;
    4. beta环境测试通过后,将代码合并到master分支,并且将代码同步到生产环境;
    5. 生产环境上线后,就再从master分支打一个tag,其作用和稳定分支stable、发布分支release一样,用于回滚代码,命名为tag-xxx,其中xxx自定义即可,如版本号。

    如果线上的代码一直没问题,自然是万事大吉,但难免会遇到各种各样的问题。这时,我们就遇到了另一种场景,即 BUG 修复。

    • 如果是 BUG 修复,则从master拉取新分支,命名为hotfix-1xx-2xx-3xx,其中每一部分都有不同的含义,如

      • hotfix为固定词,表示这是一个修复 BUG 的分支;
      • 1xx表示 BUG 的描述,为防止分支名过长,可以用缩写;
      • 2xx表示新分支创建的时间,格式为YYYYMMDD
      • 3xx表示新分支的创建者,姓名拼音或者英文名均可。

    给出一个 BUG 修复分支命名示例,hotfix-messageRepeat-20200711-chariesgavin,整个分支名称的含义就是,“某人在某时创建了修复某个 BUG 的新分支”。理论上来说,BUG 修复的开发、测试及代码合并的流程应该和上述的开发需求是一致的,毕竟如果生产环境出现了问题,其他前置环境肯定也是跑不掉的,修复已知问题终归是值得提倡的;但在比较紧急的情况下,没有足够的时间让我们在不同的环境进行测试,该流程也是可以简化的,大致如下:

    1. master分支拉取新的开发分支,进行编码,自测;
    2. 自测完成后,将代码直接合并到beta分支,上线到内测环境进行测试;
    3. 内测环境通过后,再将代码合并到master分支,同步到生产环境,并从master分支打一个tag,备份稳定代码;
    4. 最后,再将修复 BUG 的代码同步到不同环境的稳定分支。

    在这里,有一点可能让我们诟病,那就是分支名称太长了。确实,当我们想把更多的信息都揉进一个名称的时候,难免会遇到这样的问题!但如果是feature-1.0或者hotfix-20200710这类名称,可能开发周期稍微长一些的时候,大家都容易忘了这样的分支到底是谁创建的、实现了什么功能吧?因此,与之相比,我感觉分支名称稍微长一些还是可以接受的。

    当然,就如 Git Flow 一样,任何工作流想要起作用,都需要我们认同它、打心里接受它,然后才能自觉的遵守其规范,毕竟,公司总不至于因为我们不遵守分支命名规范而开除我们吧?公司采取硬性规定的另算。但这些工作流之所以能得到大家广泛的认同,并且流传之广,自然还是尤其魅力的,或多或少还是能够提高团队协作效率的。采取与否,您来决定!


    参考资料

    ]]>
    电商行业业务及数据库上云-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云最佳实践频道:【点击查看更多上云最佳实践
    这里有丰富的企业上云最佳实践,从典型场景入门,提供一系列项目实践方案,降低企业上云门槛的同时满足您的需求!

    场景描述

    该方案适用于新零售领域的电商行业,包括电商公司初创,满足快速搭建平台;以及中型企业应对发展阶段,满足业务快速占领市场。对于头部客户搬站,方案借鉴参考。本文重点解决阿里云资源的开通配置,以及其他云厂商或自建的MySQL搬迁到阿里云RDS。

    方案优势

    • 通过SLB流量分发,快速扩展应用对外服务能力
    • 通过Redis缓解高并发的数据读写,QPS支持万级到千万级
    • 提供高可用的数据库架构,保障数据的可用性和可靠性
    • 将电商静态资源存储在oss,通过cdn分发,提升用户
      访问体验
    • 云产品,支持随时弹性升级、扩容配置

    产品列表

    • 云服务器 ECS
    • 数据库 RDS
    • 数据传输 DTS
    • 负载均衡 SLB
    • 专有络 VPC

    bp-8.png

    直达最佳实践 》》


    bp-8.png

    ]]>
    容器Swarm集群向ACK集群灰度迁移-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云最佳实践频道:【点击查看更多上云最佳实践
    这里有丰富的企业上云最佳实践,从典型场景入门,提供一系列项目实践方案,降低企业上云门槛的同时满足您的需求!

    场景描述

    随着K8S生态的完善,越来越多的客户需要从Swarm集群迁移向ACK集群,本实践向您介绍阿里云上的容器Swarm集群向ACK集群灰度迁移。

    解决问题

    • 迁移过程中维持业务的延续性
    • 迁移过程业务高可用
    • 迁移过程可灰度
    • 迁移过程可回滚
    • 迁移进度可把控

    产品列表

    • 专有网络 VPC
    • 云数据库 RDS MySQL
    • 容器服务 ACK
    • 云服务器 ECS

    bp-27.png

    直达最佳实践 》》


    bp-27.png

    ]]>
    金融行业从经典网络向VPC容器化改造-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云最佳实践频道:【点击查看更多上云最佳实践
    这里有丰富的企业上云最佳实践,从典型场景入门,提供一系列项目实践方案,降低企业上云门槛的同时满足您的需求!

    场景描述

    本实践介绍经典网络向VPC容器化改造实践以及配置步骤,可适用于金融等行业。

    解决问题

    • 经典网络管理困难
    • 应用发布不灵活
    • 运维效率低

    产品列表

    • 专有网络 VPC
    • 容器服务 Kubernetes版
    • 日志服务 SLS
    • 云数据库 RDS版
    • NAT网关
    • 容器镜像服务 ACR

    bp-43.png

    直达最佳实践 》》

    bp-43.png

    ]]>
    弹性裸金属自建ORACLE数据库单机版-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云最佳实践频道:【点击查看更多上云最佳实践
    这里有丰富的企业上云最佳实践,从典型场景入门,提供一系列项目实践方案,降低企业上云门槛的同时满足您的需求!

    场景描述

    本文重点解决在弹性裸金属(神龙)服务器上自建ORACLE数据库单机的问题,通过ESSD和神龙的搭配使用为业务系统提供强大的数据库性能支撑,展示云上数据库在备份和恢复上的优势。

    解决问题

    • 如何利用云上强劲资源,如神龙服务器、ESSD存储,支撑数据库高效稳健运行。
    • 如何利用云上资源和产品优势兼顾单机数据库的可用性。
    • 如何快速备份和恢复数据库数据,保证云上数据的安全性。

    产品列表

    • 专有网络 VPC
    • 弹性公网IP
    • 弹性裸金属服务器
    • 块存储

    bp-49.png

    直达最佳实践 》》

    bp-49.png

    ]]>
    Spring 5 中文解析数据存储篇-理解Spring事物抽象-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Spring核心篇章:

    Spring 5 中文解析之核心篇-IoC容器

    Spring 5 中文解析核心篇-IoC容器之依赖关系

    Spring 5 中文解析核心篇-IoC容器之Bean作用域

    Spring 5 中文解析核心篇-IoC容器之自定义Bean性质

    Spring 5 中文解析核心篇-IoC容器之BeanDefinition继承与容器拓展点

    Spring 5 中文解析核心篇-IoC容器之基于注解的容器配置

    Spring 5 中文解析核心篇-IoC容器之类路径扫描和组件管理

    Spring 5 中文解析核心篇-IoC容器之JSR330标准注解

    Spring 5 中文解析核心篇-IoC容器之基于Java容器配置

    Spring 5 中文解析核心篇-IoC容器之Environment抽象

    Spring 5 中文解析核心篇-IoC容器之ApplicationContext与BeanFactory

    Spring 5 中文解析核心篇-IoC容器之Resources

    Spring 5 中文解析核心篇-IoC容器之数据校验、数据绑定和类型转换

    Spring 5 中文解析核心篇-IoC容器之SpEL表达式

    Spring 5 中文解析核心篇-IoC容器之AOP编程(上)")

    Spring 5 中文解析核心篇-IoC容器之AOP编程(下)")

    Spring 5 中文解析核心篇-IoC容器之Spring AOP API

    Spring测试篇章:

    Spring 5 中文解析测试篇-Spring测试

    Spring 5 中文解析核心篇-集成测试之概要和集成测试注解

    Spring 5 中文解析核心篇-集成测试之TestContext(上)")

    Spring 5 中文解析核心篇-集成测试之TestContext(中)")

    Spring 5 中文解析测试篇-集成测试之TestContext(下)")

    Spring 5 中文解析测试篇-Spring MVC测试框架

    Spring 5 中文解析测试篇-WebTestClient

    Spring存储篇章:

    Spring 5 中文解析数据存储篇-Spring框架的事物支持模型的优势

    完整电子书地址

    1.2 理解Spring框架事物抽象

    Spring事务抽象的关键是事务策略的概念。事务策略由TransactionManager定义,特别是用于命令式事务管理的org.springframework.transaction.PlatformTransactionManager接口和用于响应式事务管理的org.springframework.transaction.ReactiveTransactionManager接口。以下清单显示了PlatformTransactionManager API的定义:

    public interface PlatformTransactionManager extends TransactionManager {
    
        TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    
        void commit(TransactionStatus status) throws TransactionException;
    
        void rollback(TransactionStatus status) throws TransactionException;
    }

    尽管你可以从应用程序代码中以编程方式使用它,但它主要是一个服务提供接口(SPI)。由于PlatformTransactionManager是接口,因此可以根据需要轻松对其进行模拟或存根。它与JNDI之类的查找策略无关。与Spring框架IoC容器中的任何其他对象(或bean)一样,定义了PlatformTransactionManager实现。这一优点使Spring框架事务成为值得抽象的,即使在使用JTA时也是如此。与直接使用JTA相比,你可以更轻松地测试事务代码。

    同样,为了与Spring的理念保持一致,可以由任何PlatformTransactionManager接口方法抛出的TransactionException未检查异常(也就是说,它扩展了java.lang.RuntimeException类)。事物基础架构故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获和处理TransactionException。实际一点是,开发人员没有被迫这样做。

    getTransaction(..)方法根据TransactionDefinition参数返回TransactionStatus对象。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可能表示一个新事务或一个现有事务。后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与执行线程相关联。

    从Spring框架5.2开始,Spring还为使用响应式类型或Kotlin协程的响应式应用程序提供了事务管理抽象。以下清单显示了由org.springframework.transaction.ReactiveTransactionManager定义的事务策略:

    public interface ReactiveTransactionManager extends TransactionManager {
    
        Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
    
        Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
    
        Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
    }

    响应式事务管理器主要是服务提供接口(SPI),尽管你可以从应用程序代码中以编程方式使用它。由于ReactiveTransactionManager是接口,因此可以根据需要轻松对其进行模拟或存根。

    TransactionDefinition接口指定:

    • 传播:通常,事务范围内的所有代码都在该事务中运行。但是,如果在已存在事务上下文的情况下运行事务方法,则可以指定行为。例如,代码可以在现有事务中继续运行(常见情况),或者可以暂停现有事务并创建新事务。Spring提供了EJB CMT熟悉的所有事务传播选项。要了解有关Spring中事务传播的语义的信息,请参阅事务传播
    • 隔离:此事务与其他事务的工作隔离的程度。例如,此事务能否看到其他事务未提交的写入?
    • 超时:该事务在超时之前将运行多长时间,并由基础事务基础结构自动回滚。
    • 只读状态:当代码读取但不修改数据时,可以使用只读事务。在某些情况下,例如使用Hibernate时,只读事务可能是有用的优化。

    这些设置反映了标准的事物概念。如有必要,请参考讨论事务隔离级别和其他核心事务概念的资源。了解这些概念对于使用Spring框架或任何事务管理解决方案至关重要。

    TransactionStatus接口为事务代码提供了一种控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对于所有事务API都是通用的。以下清单显示了TransactionStatus接口:

    public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    
        @Override
        boolean isNewTransaction();
    
        boolean hasSavepoint();
    
        @Override
        void setRollbackOnly();
    
        @Override
        boolean isRollbackOnly();
    
        void flush();
    
        @Override
        boolean isCompleted();
    }

    无论你在Spring中选择声明式还是编程式事务管理,定义正确的TransactionManager实现都是绝对必要的。通常,你可以通过依赖注入来定义此实现。TransactionManager实现通常需要了解其工作环境:JDBCJTAHibernate等。

    TransactionManager实现通常需要了解其工作环境:JDBCJTAHibernate等。以下示例显示了如何定义本地PlatformTransactionManager实现(在这种情况下,使用纯JDBC)。

    你可以通过创建类似于以下内容的bean来定义JDBC数据源:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    然后,相关的PlatformTransactionManager Bean定义将引用DataSource定义。它应类似于以下示例:

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    如果你在Java EE容器中使用JTA,则可以使用通过JNDI获得的容器DataSource以及Spring的JtaTransactionManager。以下示例显示了JTAJNDI查找:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/jee
            https://www.springframework.org/schema/jee/spring-jee.xsd">
    
        <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
    
        <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
    
        <!-- other <bean/> definitions here -->
    
    </beans>

    JtaTransactionManager不需要了解数据源(或任何其他特定资源),因为它使用了容器的全局事务管理基础结构。

    dataSource bean的先前定义使用jee名称空间中的标记。有关更多信息,参考JEE Schema

    你还可以轻松使用Hibernate本地事务,如以下示例所示。在这种情况下,你需要定义一个Hibernate LocalSessionFactoryBean,你的应用程序代码可使用该Hibernate LocalSessionFactoryBean获取Hibernate Session实例。

    DataSource bean定义与先前显示的本地JDBC示例相似,因此在以下示例中未显示。

    如果通过JNDI查找数据源(由任何非JTA事务管理器使用)并由Java EE容器管理,则该数据源应该是非事务性的,因为Spring框架(而不是Java EE容器)管理事务。

    在这种情况下,txManager bean是HibernateTransactionManager类型。就像DataSourceTransactionManager需要引用数据源一样,HibernateTransactionManager需要引用SessionFactory。以下示例声明了sessionFactorytxManager bean:

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=${hibernate.dialect}
            </value>
        </property>
    </bean>
    
    <bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    如果使用Hibernate和Java EE容器管理的JTA事务,则应使用与前面的JDBC JTA示例相同的JtaTransactionManager,如以下示例所示:

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

    如果使用JTA,则无论使用哪种数据访问技术(无论是JDBCHibernate JPA或任何其他受支持的技术),事务管理器定义都应该相同。这是由于JTA事务是全局事务,它可以征用任何事务资源。

    在所有这些情况下,无需更改应用程序代码。你可以仅通过更改配置来更改事务的管理方式,即使更改意味着从本地事务转移到全局事务,反之亦然。

    作者

    个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!

    博客地址: http://youngitman.tech

    CSDN: https://blog.csdn.net/liyong1028826685

    微信公众号:

    技术交流群:

    ]]>
    如何在Joomla中删除组件菜单链接-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 有时,Joomla用户已删除组件,但仍在其管理区域中看到该组件的菜单项。

    在本教程中,您将学习如何摆脱不必要的菜单项。Akeeba订阅将用作示例组件。

    您可以在下图中看到以语言字符串形式渲染的不需要的菜单项:

    deleted-components-menu-01.png

    要彻底删除它,请打开您站点的数据库。

    找到#__menu表,其中#__是您的数据库前缀。单击此数据库表。

    deleted-components-menu-02.png

    浏览表记录,直到找到由不需要的组件创建的记录。

    单击删除。

    deleted-components-menu-03.png

    您将看到“确认”框,提示您确认您确实要删除数据库表记录。

    单击确定。

    deleted-components-menu-04.png

    您将看到您的组件记录不再在表中列出:

    deleted-components-menu-05.png

    在您的Joomla控制面板中,转到“组件”菜单。您将看到那里也没有列出不需要的菜单项:

    deleted-components-menu-06.png

    ]]>
    架构设计 | 分布式体系下,服务分层监控策略-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、分布式故障

    分布式系统的架构,业务开发,这些在良好的思路和设计文档规范之下,是相对来说好处理的,这里的相对是指比较分布式架构下生产环境的突然故障。

    在实际的开发中,有这样一个很妖娆的情况:越是核心复杂的业务,越是担心出问题,越容易出问题。

    13-1.png

    所以当核心服务的链路出现故障时,如何快速定位问题就是一件很头疼的事情,尤其是一些特殊情况下,问题很模糊很难复现,外加客户或者领导催促,这种场景心里阴影是大部分开发都有的。更有甚者,可能问题发生的切入点的开发是某人负责的,实际问题是发生在请求链路的其他服务上,这种情况遇多了,甩锅水平会直线上升。

    越是复杂的系统,越是经验丰富的开发或者运维,对监控系统就越是有执念,尤其是全链路的监控,底层,网络,中间件,服务链路,日志观察预警等,用来快速定位问题,省时省心。

    二、全链路监控

    1、监控层次

    在分布式系统中,需要监控的体系和层次极其复杂,通常整体上划分为三个层次:应用服务,软件服务,硬件服务。

    13-2.png

    通常情况,运维管理硬件服务,开发管理应用和软件服务。

    2、应用服务

    应用层为开发的业务逻辑服务,也是最容易突发问题的一个层面,当在一家公司待久了,因为开发过多个业务线,就会感觉自己不是开发,是个打杂的,每天都要分出大量时间处理各种问题。应用层监控涉及下面几个核心模块:

    请求流量

    任何服务,高并发的流量都会暴露各种服务问题,尤其核心接口的流量更是监控的重点。

    服务链路

    一次请求发生问题,快速判断问题所在的服务,或者哪些服务之间,这对快速处理问题是至关重要的。

    日志体系

    核心接口日志记录也是必备的功能,通常情况下基于日志体系的分析结果,可以明确系统的异常点,重点优化。

    3、软件服务

    为了解决分布式系统的各种复杂业务场景,通常会引入各种中间软件来做支撑,例如必备的数据库,缓存,消息MQ等,通常这些中间件都会有自带的监控管理端口。

    数据库:较多使用Druid监控分析;

    消息队列:常用RocketMQ和控制台;

    Redis缓存:提供命令获取相关监控数据;

    还有一些公司甚至直接在中间件层开发一套管理运维和监控的聚合平台,这样更容易从整体上分析问题。

    4、硬件服务

    硬件层面,运维最关注的三大核心内容:CPU、内存、网络。底层硬件资源爆发的故障,来自上层的应用服务或者中间件服务触发的可能性偏高。

    硬件层面的监控有许多成熟的框架,例如zabbix,grafana等,当然这些组件功能很丰富,不仅仅在硬件层应用。

    5、雪崩效应

    有些故障导致大面积服务瘫痪,也称为雪崩效应,可能故障源没有快速处理,也没有熔断机制,导致整个服务链路全部垮掉,这是常见的问题,所以在处理故障时,要学会基于全栈监控信息,全局关联分析核心故障点,快速切断单点服务的故障,保证整个系统的可用性。

    三、注意事项

    监控系统虽然作用很大,但是实际搭建的时候难度还是很大,需要有较好的意识,不是业务开发那种感觉,方方面面需求都需要处理,做监控系统的基本策略如下。

    1、选择性

    不是所有服务的所有环境,和所有接口都需要监控,通常都是监控核心链路,核心中间件,和服务所在环境。

    例如:交易链路,交易库,和部署的环境;或者大客户高并发业务,一旦出问题需要及时响应,立即处理。说的直接点,带来收益的服务是需要重点关注的。

    非关键服务即使出现问题,是有缓冲时间的,所以不需要花费精力添加监控,在做监控系统的时候存在这样一句话:简单的链路添加监控,复杂了容易出错;复杂链路添加监控,更复杂更容易出错,然而这样却是为了更好的解决故障。

    2、独立性

    监控系统的本身发生故障,不能影响正常业务流程,即使在一定情况下没有监控信息,也不能因为监控服务影响正常业务服务。

    3、整体性

    聚合的监控系统可以观察监控链路的全局状态,这样可以快速定位故障坐标,可以关联性分析问题原因。

    4、预警性

    例如CPU突然升高,某个中间件服务突然停止,内存占用过高,这些可以基于监控系统做预警通知,然后邮件或者消息通知到相关负责人,达到快速响应的目的,这个场景大部分开发都熟悉,且有心理阴影。

    ]]>
    大数据简介,技术体系分类整理-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、大数据简介

    1、基础概念

    大数据是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。大数据技术则主要用来解决海量数据的存储和分析。

    2、特点分析

    大数据的5V特点(IBM提出):Volume(大量)、Velocity(高速)、Variety(多样)、Value(低价值密度)、Veracity(真实性)。

    3、发展过程

    Google在2004年前后发表的三篇论文,分别是文件系统GFS、计算框架MapReduce、NoSQL数据库系统BigTable。海量数据文件,分析计算,并存储,确立了大数据的基本原理和思路。

    天才程序员DougCutting,也是Lucene、Nutch项目发起人。根据Google论文原理初步实现类似GFS和MapReduce的功能,后来发展成为大名鼎鼎的Hadoop。

    再后来,Hadoop经过高速的发展,已经形成一个生态体系,基于Hadoop之上,有实时计算,离线计算,NoSQL存储,数据分析,机器学习等一系列内容。

    从这一系列事情发展看技术规律:Google业务实践中创造性的提出论文作为基础,业务的成长和需求,迫使技术不断更新换代。所以业务是技术不断发展的关键。

    二、Hadoop框架

    1、Hadoop简介

    注意这里基于Hadoop2.X版本描述。后续如果没有特别说明,都是2.7版本。

    01-1.png

    Hadoop是一个由Apache基金会所开发的分布式系统基础架构;

    提供海量的数据存储能力,和分析计算能力;

    作为Apache的顶级项目,包含众多子项目是一个生态圈;

    2、框架特点

    可靠性:Hadoop按位存储和存储多个数据副本,提供可靠服务;

    扩展性:Hadoop利用计算机集群分配数据并完成计算任务,可以方便地扩展到数以千计的节点中;

    高效性:基于MapReduce思想,为海量的数据提供高效的并行计算;

    容错性:自动保存数据的多个副本,并且能够自动将失败的任务重新分配;

    3、组成结构

    HDFS存储

    • NameNode

    存储文件相关的元数据,例如:文件名,文件目录,创建时间,权限副本数等。

    • DataNode

    文件系统存储文件块数据,以及和数据块ID的映射关系。

    Yarn调度

    负责资源管理和作业调度,将系统资源分配给在Hadoop集群中运行的各种应用程序,并调度要在不同集群节点上执行的任务。

    MapReduce计算

    MapReduce将计算过程分为两个阶段:Map阶段并行处理输入数据,Reduce阶段对Map结果进行汇总。

    三、大数据技术栈

    01-2.png

    1、Kafka中间件

    开源组织: Apache软件

    应用场景:

    Kafka是一种高吞吐量的分布式发布订阅消息系统,通过磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息。支持通过Kafka服务器和消费机集群来分区消息。支持Hadoop并行数据加载。

    2、Flume日志系统

    开源组织: Cloudera公司

    应用场景:

    Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。

    3、Sqoop同步工具

    开源组织: Apache软件

    应用场景:

    Sqoop是一款开源的工具,主要用于在Hadoop、Hive与传统的数据库例如:MySql间进行数据的传递,可以将一个关系型数据库(例如:MySQL,Oracle 等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。

    4、HBase数据库

    开源组织: Apache软件

    应用场景:

    HBase是一个分布式的、面向列的开源数据库,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库,并且基于列的而不是基于行的存储模式。

    5、Storm实时计算

    开源组织: Apache软件

    应用场景:

    Storm用于实时计算,对数据流做连续查询,在计算时就将结果以流的形式输出给用户。Storm相对简单,可以与任何编程语言一起使用。

    6、Spark计算引擎

    开源组织: Apache软件

    应用场景:

    Spark是专为大规模数据处理而设计的快速通用的计算引擎,拥有Hadoop的MapReduce所具有的优点;但不同于MapReduce的是——Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。Spark是在Scala 语言中实现的,它将Scala用作其应用程序框架。

    7、R语言

    开源组织: 微软公司

    应用场景:

    R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个自由、免费、源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具。

    8、Hive数仓工具

    开源组织: 脸书公司

    应用场景:

    hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表,并提供SQL查询功能,能将SQL语句转变成MapReduce任务来执行。

    9、Oozie组件

    开源组织: Apache软件

    应用场景:

    Oozie是一个管理Hdoop作业(job)的工作流程调度管理系统。

    10、Azkaban组件

    开源组织: Linkedin公司

    应用场景:

    批量工作流任务调度器。用于在一个工作流内以一个特定的顺序运行一组工作和流程。Azkaban定义了一种KV文件格式来建立任务之间的依赖关系,并提供一个易于使用的web用户界面维护和跟踪的工作流。

    11、Mahout组件

    开源组织: Apache软件

    应用场景:

    Mahout提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建智能应用程序。Mahout包含许多实现,包括聚类、分类、推荐过滤、频繁子项挖掘。

    12、ZooKeeper组件

    开源组织: Apache软件

    应用场景:

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    四、技术栈分类

    存储体系:Hadoop-HDFS、HBase、MongoDB、Cassandra

    计算体系:Hadoop-MapReduce、Spark、Storm、Flink

    数据同步:Sqoop、DataX

    资源调度:YARN、Oozie、Zookeeper

    日志收集:Flume、Logstash、Kibana

    分析引擎:Hive、Impala、Presto、Phoenix、SparkSQL

    集群监控:Ambari、Ganglia、Zabbix

    五、源代码地址

    GitHub·地址
    https://github.com/cicadasmile/big-data-parent
    GitEE·地址
    https://gitee.com/cicadasmile/big-data-parent
    ]]>
    穿越数据的变迁(数据闪回PR)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在当下数据为王的时代,客户的业务数据是一个企业的核心资产,各个行业客户都在不断追求在其使用的数据库上有更为强大、细粒度的数据备份恢复功能,以应对各样的数据丢失、业务逻辑错误带来的商业风险。例如,在游戏行业里,有大量客户存在“游戏回档”的实际需求,以应对运营或故障风险。再例如,2020年某上市公司出现删库跑路事件,公司市值遭到严重打击。对于上述情况,传统的定时或手动备份的数据由于存在备份时间点与黑天鹅事件之间的时间差,因此均不是理想完备的解决方案。对于防范上述数据风险,最理想的功能是,数据能够以秒级颗粒度恢复至黑天鹅事件发生的瞬时时间点上。

    在传统关系型数据库中(如Oracle)提供了数据闪回功能,帮助客户完成数据的按时间恢复。而在主流的高速缓存产品Redis、Memcached上却鲜有类似的功能,个中原因主要是考虑到作为缓存产品,其中的数据可以丢失,而通过背后配备的持久化、事务性的关系型数据库可以对缓存进行重新加载。但是,随着大量客户对于低延迟的业务应用性能要求不断的提高,对于一些对延迟要求极高的场景下,例如游戏行业,越来越多的客户选择化简架构,将Redis直接作为内存数据库来使用,这便对Redis的数据安全可恢复提出了更大的挑战。阿里云Redis作为行业内的领军产品,拥有国内最强大的Redis产品系列,在托管社区Redis版本的同时大力发展自主研发、兼容Redis协议增强型键值存储产品Tair(阿里云Redis企业版)。其中“更安全”是阿里云Redis向广大客户提供服务的重要部分,针对上述客户诉求,阿里云在Tair中的性能增强版上特别推出了数据闪回功能,帮助客户实现在其指定时间点上恢复指定Redis实例数据的能力,提前为客户可能出现的风险准备好一剂“后悔药”。功能示意如下图所示:
    使用这个能够帮助用户穿越数据变迁的黑科技只需简单几步。
    • 开通阿里云Redis企业版性能增强版实例
    • 开启数据闪回功能
    • 正常读写Redis数据库
    • 一旦发生即时数据恢复要求,选择对应实例和欲恢复的时间点,产生新实例
    • 切换应用连接的实例地址
    下面以一个简单的示例演示:

    首先,我们先对一个阿里云Tair实例写入一些数据,写入脚本如下:
    % cat preparerepldata.sh

    !/bin/bash

    echo "started at: date +%m%d%H%M%S"
    for i in seq -f "%010g" 1 $1
    do

    namerand=$[ $RANDOM % 3 ]
    timerand=`date +%m%d%H%M%S`
    nationrand=$[ $RANDOM % 3 ]
    redis-cli -h   r-t4nq2viog3z4rshoxxpd.redis.singapore.rds.aliyuncs.com -a 'gnuhpc:Pa$$w0rd'   hset userprofile foo$timerand $namerand
    sleep 0.75 #consider the   latency for connection establish

    done

    echo "ended at: date +%m%d%H%M%S"

    我们运行上述脚本以大约一秒的速率写入带时间戳的KV到一个叫做userprofile的hash结构中,一共写入120个Key,代表业务写入。

    % bash preparerepldata.sh 120
    OK
    OK

    % redis-cli -h hosts -a 'username:password' hkeys userprofile | wc -l

     120

    % redis-cli -h hosts -a 'username:password' hkeys userprofile | sort -n
    foo0803202709

    foo0803202909

    如命令执行所示,我们是在08月03日20:27:09 –08月03日 20:29:09 写入了120条数据。

    随后在业务运行中,发现08月03日20:28:10之后,上述业务运行中发生数据风险,我们需要将新创建一个数据库,将原数据库在此时间点之前的数据恢复到这个新的数据库实例中。

    我们到阿里云Redis控制台的备份恢复功能,选择数据闪回功能,点击“马上闪回”按钮,选择克隆源类型是“过去时间点”,然后选择要恢复的时间点:

    新建的实例就是我们要将指定时间点数据恢复到的目标实例,这里值得一提的数据闪回只要求目标实例容量大于等于源实例,而架构则可以与源实例不同,也就是说用户可以自由选择目标实例为标准主从、集群或者读写分离架构的任何一个。

    点击支付后等待片刻后实例创建完毕并完成数据恢复。

    我们连接新创建的实例,看看究竟数据恢复的情况如何?

    % redis-cli -h hosts -a 'username:password' hkeys userprofile | wc -l
    62

    % redis-cli -h hosts -a 'username:password' hkeys userprofile | sort -n
    foo0803202709

    foo0803202810
    可以看到,我们已经成功的恢复数据到了08月03日 20:28:10的数据,在此时间点之后的数据修改均没有生效,用户完美地规避了指定的时间点之后发生的数据风险。

    作为全球数据库领域最领先的企业之一,阿里云数据库的核心理念是更快、更稳、更安全(Faster, Stronger, Securer)的数据库,致力于为用户提供拥有极致性能、高稳定性和更加安全可靠的数据库产品。作为“更安全”的践行,阿里云Redis团队在Redis企业版(Tair)中提供的数据闪回功能,具备了用户指定秒级时间点的数据恢复功能,通过此功能为企业的高贵数据资产做最高级别的保驾护航,客户可以更加放心的将Redis作为内存数据库使用以简化架构与业务的快速迭代。另外,我们云数据库专属集群MyBase还支持Redis,这是阿里云专为大中型企业用户定制优化的解决方案,填补了市场空白,具有资源独享、自主可运维、多数据库混合部署等特点,让用户既享受到云数据库的灵活性,又满足企业对数据库合规性、高性能和安全性要求。
    ========微博消息版========
    让您不再有“从删库到跑路”的噩梦!阿里云Redis企业版性能增强版特别推出数据闪回功能,帮助客户实现在其指定时间点上恢复指定Redis实例数据的能力,详见https://help.aliyun.com/document_detail/148479.html

    ]]>
    2020云栖大会来了!阿里云数据库6大专场重磅发布技术突破-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 6个分论坛预热广告.png

    ]]>
    SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(优雅上下线)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 头图.png

    作者 | 骄龙

    导读:本篇是《SpringCloud 应用在 Kubernetes 上的最佳实践》系列文章的第八篇,主要介绍了如何做到流量的无损上/下线。更多相关文章阅读可查看文末。

    前言

    上篇我们讲的是发布回滚过程,尤其是在 Kubernetes 的回滚过程中,原生有提供 Rollout 到上一个版本的能力,能保证我们在发布过程中遇到问题时快速回退的能力。然而在每一次上线的过程中,我们最难处理的就是正在运行中的流量,如何做到流量的无损上/下线,是一个系统能保证 SLA 的关键。

    介绍

    什么是优雅上线?就如下面这个房子一样,未建好的房子,人住进去会有危险,房子应该建好,装修好,人才能住进去。

    1.jpeg

    那么如何做到优雅上线呢?我们先来看一个 WEB 应用的加载过程,就像上面造房子一样,是个漫长的过程:

    2.png

    应用的加载是漫长的,在加载过程,服务是不可预期的;如过早地打开 Socket 监听,则客户端可能感受到漫长的等待;如果数据库、消息队列、REDIS 客户端未完成初始化,则服务可能因缺少关键的底层服务而异常。

    所以在应用准备完成后,才接入服务,即做到优雅上线。当然应用上线后,也可能因如数据库断连等情况引起服务不可用;或是准备完成了,但在上线前又发生数据库断连,导致服务异常。为了简化问题,后面两种情况作为一个应用自愈的问题来看待。

    什么是优雅下线?与建房子相反就像下面的危房一样,人住在里面很危险,人应该先从房子出来,然后推掉房子。

    3.jpeg

    那么如何做到优雅下线呢?我们先来看一个 WEB 应用的停止过程:

    4.png

    所以关闭服务接入(转移服务接入),完成正在处理的服务,清理自身占用的资源后退出即做到优雅下线。

    如何实现优雅下线

    从上面介绍看,似乎不难,但事实上,很少有系统真正实现了优雅上下线。因为软件本身由无数各种各样相互依赖的结构组成,每个结构都使用一些资源,污染一些资源;通常在设计之初优雅上下线也不被作为优先考虑的需求,所以对于下线的过程,通常都没被充分考虑,在设计上通常要求:

    • 结构(组件)应形成层次关系;
    • 用户线程需能收到停止信号并响应退出;否则使用 daemon 线程;
    • 结构应按依赖关系自下向上构建:就像建房子一样,自内向外构建而成;
    • 结构应按依赖关系自上向下销毁:就像拆房子一样,自外向内拆解。

    优雅下线实现路径

    大致分为一个完整的过程,需要经历一下四个关键的节点,如下图:

    5.png

    • 接收信号:停止信号可能从进程内部触发(比如 Crash 场景),如果自退出的话基本上无法保证优雅下线;所以能保证优雅下线的前提就是需要正确处理来自进程外部的信号;
    • 停止流量接收:由于在停止之前,我们会有一些正在处理的请求,贸然退出会对这些请求产生损耗。但是在这段时间之内我们绝不能再接收新的业务请求,如果这是一个后台任务型(消息消费型或任务调度型)的程序,也要停止接收新的消息和任务。对于一个普通的 WEB 场景,这一块不同的场景实现的方式也会不一样,下面的 Srping Cloud 应用的下线流程会详细讲解;
    • 销毁资源:常见的是一些系统资源,也包括一些缓存、锁的清理、同时也包括线程池、关闭阻塞中的的 IO 操作,等到我们这些服务器资源销毁之后,就可以通知主线程退出。

    Spring Cloud 应用

    一个 Spring boot 应用通常由应用本身和一系列的 Starter 组成,对于 Spring boot 体系,需要了解如下核心概念:

    • Starter:提供一系列的模块,由 Spring boot 核心通过 auto-configuration 机制加载;
    • Bean:一切皆 Bean,starter 模块的加载产生各种 Bean;
    • Context:Bean 的容器,容器拥有生命周期,Bean 需要感知生命周期事件;
    • LifeCycle:生命周期管理接口;
    • ApplicationEvent:模块之间,模块与容器之间,通过发送或监听事件来达到互相通讯的目的。

    所以对于应用上下线这个主题,我们应尽可能利用其丰富的原生事件机制,Spring Cloud 中内置的 Starter 机制针对整个生命周期管理的过程有了很好的封装。

    Spring Cloud 应用的优雅上线

    Spring Cloud 启动过程触发回调及事件如下,详细介绍见 application-events-and-listeners,简单罗列如下:

    6.png

    Spring 自身及其组件大量基于这些事件构建,如响应 WebServerInitializedEvent 事件向服务注册中心注册服务,对于应用一般可利用:

    • InitializingBean or @PostConstruct:在 Bean 装配完后,被回调,如完成数据源初始化连接;
    • ApplicationReadyEvent、ApplicationRunner、CommandLineRunner:如开始监听消息队列,处理消息;注册到SLB等;先通过配置禁用服务的自动注册,在这里做手动服务注册。

    Spring Cloud 应用的优雅下线

    Spring Cloud 本身可以作为一个应用单独存在,也可以是依附在一个微服务集群中,同时还能作为反向代理架构中的一个网关。不同的场景,需要用到的方法也不一样,我们就常用的三种场景针对性的加以说明。

    场景一:直接访问 WEB 服务

    7.png

    客户端直接访问 WEB 应用,在这个用例下,优雅下线需要做的事情有:

    • 正在处理的请求完成处理
    • 应用自身完成安全下线并正常退出
    • 客户端感知到连接异常

    Spring-boot 从 2.3 开始内置了 WEB 应用优雅下线的能力,需配置如下,具体介绍参见 graceful-shutdown

    server.shutdown=graceful
    spring.lifecycle.timeout-per-shutdown-phase=20s

    其实现方式:

    • 首先关闭 socket 监听,等待正在处理的所有请求完成:具体可见 WebServerGracefulShutdownLifecycle,通过 getPhase 返回最大值,达到早于 WEB 容器关闭执行的目的;
    • 然后触发 WEB 容器关闭:具体可见 WebServerStartStopLifecycle。

    但其实,对于未被 WEB 容器完全接收的请求,客户端仍会收到连接被重置的异常,只是这个时间窗口极小。该需求从提出到实现的时间跨度较长,感兴趣的可参见 github 上的讨论

    场景二:经由反向代理的服务优雅下线

    8.jpeg

    因为实例前面还有反向代理,相比上个场景,需要新增“反向代理下线”这个处理流程。即若应用已经下线,但反向代理未摘除该应用实例时客户端将感知到失败。一般采取的策略有:

    • 反向代理支持失败转移到其它应用实例;
    • 在关闭应用前,如将健康探测接口返回不健康以及等待足够的超时,让反向代理感知并摘除实例的路由信息。

    对于仍在使用 2.3 以前版本的 Spring Cloud 应用,可参见一个方案,实现方式:

    • 使用自身的 shutdownHook 替换 Spring 的 shutdownHook;
    • 先改变 health 状态,等待一段时间,让反向代理感知并摘除实例的路由信息。

    场景三:在微服务集群中下线单个服务

    9.jpeg

    在优雅关闭 Spring Cloud 应用自身之前,我们除了完成场景一之中的目标之外,还需要将自身节点从注册中心中下线。目前在 Spring Cloud 中针对注册中心下线的场景暂未提供开箱即用的方法,下面介绍两种可能的实现方案:

    方案 1:先通过脚本、或通过监听 ContextClosedEvent 反注册服务摘除流量;等待足够时间,如使用 ribbon 负载均衡器,需要长于配置的刷新时间;对于基于 HTTP 的服务,若 Spring Cloud 版本小于 2.3,则时间需加上预期的请求处理时间;

    方案 2:客户端支持连接感知重试,如重试,实现方案可参考Spring-retry,针对连接异常 RemoteConnectFailureException 做重试。

    针对 Eureka 中的场景,有一个很好的参考的例子,请参见:https://home1-oss.github.io/home1-oss-gitbook/release/docs/oss-eureka/GRACEFUL_SHUTDOWN.html

    Kubernetes 下的机制

    Kubernetes 中针对应用的的管控提供了丰富的手段,正常的情况它提供了应用生命周期中的灵活扩展点,同时也支持自己扩展它的 Operator 自定义上下线的流程。

    10.jpeg

    抛开实现成本,以下线的情况来说,一个 Kubernetes 应用实例下线之前,管控程序会向 POD 发送一个 SIGTERM 的信号,应用响应时除了额外响应这一个信号之外,还能触发一段自定义的 PreStop 的挂在脚本,代码样例如下:

    yaml
    lifecycle:                   
          preStop:                   
            exec:                    
              command:               
              - sh
              - -c
              - "sleep 5"

    上面的例子一点特殊说明:因服务控制面刷新与 POD 收到 SIGTERM 同时发生,所以这里通过 sleep 5 让服务控制面先完成刷新,应用进程再响应 SIGTERM 信号。

    Spring Cloud 与 Kubernetes 的结合

    Kubernetes 会根据健康检查的情况来更新服务(Service)列表,其中如果 Liveness 失败,则会触发容器重建,这是一个相对很重的操作;若 Readiness 失败,则 Kubenetes 则默认不会将路由服务流量到相应的容器;基于这一机理,Spring Cloud 2.3 开始,也做了原生的的支持,具体参见 liveness-and-readiness-probes-with-Spring-boot,这些健康检查端点可对接 Kubnetes 相应的 probe:

    • /actuator/health/liveness
    • /actuator/health/readiness

    同时,Spring Boot 内置了相应的 API、事件、Health Check 监控,部分代码/配置片段如下:

    java
    // Available as a component in the application context
    ApplicationAvailability availability;
    LivenessState livenessState = availabilityProvider.getLivenessState();
    ReadinessState readinessState = availabilityProvider.getReadinessState();
    ....
    // 对于应用,也可以通过API,发布相应的事件,来改变应用的状态
    AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
    // 同时,应用监控也可影响这健康状态,将监控与健康关联,在K8S体系下,可以实现如离群摘除,应用自愈的能力
    // application.properties
    management.endpoint.health.group.liveness.include=livenessProbe,cacheCheck

    回到 Spring Cloud 应用 在微服务集群中下线单个服务 的章节中,我们的应用如果跑在 Kuberntes 中,如果我们使用了原生的 Kubernetes 机制去管理应用生命周期的话,只需要发布一个应用事件 (LivenessState.BROKEN) 即可实现优雅下线的能力。

    EDAS提供内置的优雅上下线能力

    通过上面两部分了解了 Spring Cloud 和 K8s 中的机制,EDAS 基于原生的机制,衍生出来了自己的方法,除了最大化利用这些能力:主动更新 Liveness、Readiness、Ribbon 服务列表之外,我们还提供了无代码侵入的开箱即用的能力,列举如下:

    后续

    这一章节之后,和发布相关的内容都已经更新完毕,下一章节我们要开始高可用部分的能力,高可用也是系统保障 SLA 的关键部分,简单的理解是流量洪峰到来如何保证系统不会受到影响?当然我们还有一部分要达成的是洪峰退去之后资源是否存在浪费?敬请期待 ...

    相关文章推荐:

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    数据湖构建服务搭配Delta Lake玩转CDC实时入湖-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 什么是CDC

    Change Data Capture(CDC)用来跟踪捕获数据源的数据变化,并将这些变化同步到目标存储(如数据湖或数据仓库),用于数据备份或后续分析,同步过程可以是分钟/小时/天等粒度,也可以是实时同步。CDC方案分为侵入式(intrusive manner)和非倾入性(non-intrusive manner)两种。

    1.png

    侵入式

    侵入式方案直接请求数据源系统(如通过JDBC读取数据),会给数据源系统带来性能压力。常见的方案如下:

    • 最后更新时间(Last Modified)

    源表需要有修改时间列,同步作业需要指定最后修改时间参数,表明同步某个时间点之后变更的数据。该方法不能同步删除记录的变更,同一条记录多次变更只能记录最后一次。

    • 自增id列

    源表需要有一个自增id列,同步作业需要指定上次同步的最大id值,同步上次之后新增的记录行。该方法也不能同步删除记录的变更,而且老记录的变更也无法感知。

    非侵入式

    非侵入性一般通过日志的方式记录数据源的数据变化(如数据库的binlog),源库需要开启binlog的功能。数据源的每次操作都会被记录到binlog中(如insert/update/delete等),能够实时跟踪数据插入/删除/数据多次更新/DDL操作等。

    示例:

    insert into table testdb.test values("hangzhou",1);
    update testdb.test set b=2 where a="hangzhou";
    update testdb.test set b=3 where a="hangzhou";
    delete from testdb.test where a="hangzhou";

    2.png

    通过将binlog日志有序的回放到目标存储中,从而实现对数据源的数据导出同步功能。

    常见的CDC方案实现

    开源常见的CDC方案实现主要有两种:

    Sqoop离线同步

    sqoop是一个开源的数据同步工具,它可以将数据库的数据同步到HDFS/Hive中,支持全量同步和增量同步,用户可以配置小时/天的调度作业来定时同步数据。

    sqoop增量同步是一种侵入式的CDC方案,支持Last Modified和Append模式。
    3.png

    缺点:

    • 直接jdbc请求源库拉取数据,影响源库性能
    • 小时/天调度,实时性不高
    • 无法同步源库的删除操作,Append模式还不支持数据更新操作

    binlog实时同步

    binlog日志可以通过一些工具实时同步到kafka等消息中间件中,然后通过Spark/Flink等流引擎实时的回放binlog到目标存储(如Kudu/HBase等)。

    4.png

    缺点:

    • Kudu/HBase运维成本高
    • Kudu在数据量大的有稳定性问题, HBase不支持高吞吐的分析
    • Spark Streaming实现回放binlog逻辑复杂,使用java/scala代码具有一定门槛

    Streaming SQL+Delta Lake实时入湖方案

    前面介绍了两种常见的CDC方案,各自都有一些缺点。阿里云E-MapReduce团队提供了一种新的CDC解决方案,利用自研的Streaming SQL搭配Delta Lake可以轻松实现CDC实时入湖。这套解决方案同时通过阿里云最新发布的数据湖构建(Data Lake Formation,DLF)服务提供一站式的入湖体验。

    5.png

    Streaming SQL

    Spark Streaming SQL在Spark Structured Streaming之上提供了SQL能力,降低了实时业务开发的门槛,使得离线业务实时化更简单方便。

    Spark Streaming SQL支持的语法如下:
    截屏2020-09-14 下午10.22.51.png

    下面以实时消费SLS为例:

    # 创建loghub源表
    spark-sql> CREATE TABLE loghub_intput_tbl(content string)
             > USING loghub
             > OPTIONS
             > (...) 
    # 创建delta目标表
    spark-sql> CREATE TABLE delta_output_tbl(content string)
             > USING delta
             > OPTIONS
             > (...);
    # 创建流式SCAN
    spark-sql> CREATE SCAN loghub_table_intput_test_stream
             > ON loghub_intput_tbl
             > USING STREAM;
    # 将loghub源表数据插入delta目标表         
    spark-sql> INSERT INTO delta_output_tbl SELECT content FROM loghub_table_intput_test_stream;

    Delta Lake

    Delta Lake是Databricks开源的一种数据湖格式,它在parquet格式之上,提供了ACID事务/元数据管理等能力,同时相比parquet具有更好的性能,能够支持更丰富的数据应用场景(如数据更新/schema演化等)。

    5.png

    E-MapReduce团队在开源Delta Lake基础上做了很多功能和性能的优化,如小文件合并Optimize/DataSkipping/Zorder,SparkSQL/Streaming SQL/Hive/Presto深度集成Delta等。

    15.png

    Streaming SQL+Delta Lake CDC实时入湖

    Spark Streaming SQL提供了Merge Into 的语法,搭配Delta Lake的实时写入能力,可以很方便的实现CDC实时入湖方案。

    17.png

    如上图所示,只需要SQL就能完成CDC实时入湖,细节步骤详见E-MapReduce文档

    阿里云最新发布的数据湖构建(Data Lake Formation,DLF)服务,提供了完整的一站式入湖解决方案

    ----

    更多数据湖技术相关的文章请点击:阿里云重磅发布云原生数据湖体系


    更多数据湖相关信息交流请加入阿里巴巴数据湖技术钉钉群
    数据湖钉群.JPG

    ]]>
    如何基于 Flink 生成在线机器学习的样本?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:曹富强(微博)

    在线机器学习与离线相比,在模型更新的时效性,模型的迭代周期,业务实验效果等方面有更好的表现。所以将机器学习从离线迁移到在线已经成为提升业务指标的一个有效的手段。

    在线机器学习中,样本是关键的一环。本文将给大家详细的介绍微博是如何用 Flink 来实现在线样本生成的。

    为何选择 Flink 来做在线的样本生成?

    在线样本生成对样本的时效性和准确性都有极高的要求。同样对作业的稳定性及是否容灾也都有严格的指标要求。基于这个前提,我们对目前较为流行的几种实时计算框架(Storm 0.10, Spark 2.11, Flink 1.10)进行了分析比较,结论如下:

    1.jpg

    因此,我们决定使用 Flink 来作为在线样本生成的实时流计算框架。

    如何实现?

    在线样本生成,简单描述一个业务场景:对用户的曝光数据和点击数据实时的做关联,关联后将数据输出到 Kafka 中,给下游的在线训练作业用。

    首先我们要确定两个数据流关联的时间窗口。这一步一般建议先离线对两个数据流的日志做关联,通过离线的方式对两份数据在不同的时间范围内做 join,来判断在线需要的时间窗口。比如业务接受的最低关联比例是 85%,并且通过离线测试确认 20 分钟内两个数据流可以关联 85%的数据,那么就可以采用 20 分钟作为时间窗口。这里的关联比例和窗口时间实际上是在准确性和实时性之间的一个 trade-off。

    确定时间窗口后,我们并没有使用 Flink 的 time window 来实现多个数据流的 join,而是选择采用 union + timer 方式来实现。这里主要考虑两点:第一、Flink 自带的 join 操作不支持多个数据流。第二、使用 timer+state 来实现,自定义程度更高,限制更少,也更方便。

    接下来,我们把样本生成过程细分为:

    ① 输入数据流

    一般我们的数据源包括 Kafka,Trigger,MQ 等。Flink 需要从数据源中实时的读取日志。

    ② 输入数据流的格式化和过滤

    读取日志后,对数据做格式化,并且过滤掉不需要的字段和数据。
    指定样本 join 的 key。例如:用户 id 和 内容 id 作 key。
    输出的数据格式一般为 tuple2(K,V),K:参与 join 的 key。V:样本用到的字段。

    ③ 输入数据流的 union

    使用 Flink 的 union 操作,将多个输入流叠加到一起,形成一个 DataStream。
    为每个输入流指定一个可以区分的别名或者增加一个可以区分的字段。

    ④ 输入数据流的聚合:keyby 操作

    对 join 的 key 做 keyby 操作。接上例,表示按照用户 id 和内容 id 对多个数据流做 join。
    如果 key 存在数据倾斜的情况,建议对 key 加随机数后先聚合,去掉随机数后再次聚合。

    ⑤ 数据存储 state + timer

    1. 定义一个Value State。
    2. keyby后的process方法中,我们会重写processElement方法,在processElement方法中判断,如果value state为空,则new 一个新的state,并将数据写到value state中,并且为这条数据注册一个timer(timer会由Flink按key+timestamp自动去重),另外此处我们使用的是ProcessingTime(表示onTimer()在系统时间戳达到Timer设定的时间戳时触发)。如果不为空则按照拼接的策略,更新已经存在的结果。比如:时间窗口内 用户id1,内容id1的第一条日志数据没有点击行为,则这个字段为0,第二条点击数据进入后,将这个字段更新为1。当然除了更新操作,还有计数、累加、均值等各种操作。如何在process里区分数据是来自曝光还是点击呢,使用上面步骤③定义的别名。
    3. 重写onTimer方法,在onTimer方法中主要是定义定时器触发时执行的逻辑:从value state里获取到存入的数据,并将数据输出。然后执行state.clear。
    4. 样本从窗口输出的条件有2个:第一,timer到期。第二,业务需要的样本都拼接上了。

    此处参考伪代码:

    public class StateSampleFunction extends KeyedProcessFunction<String, Tuple2, ReturnSample> {
        /**
         * 这个状态是通过过程函数来维护,使用ValueState
         */
        private ValueState state;
    
        private Long timer = null;
    
        public StateSampleFunction (String time){
            timer = Long.valueOf(time);
        }
    
        @Override
        public void open(Configuration parameters) throws Exception {
            // 获取state
            state = getRuntimeContext().getState(new ValueStateDescriptor<>("state", TypeInformation.of(new TypeHint< ReturnSample >() {})));
        }
    
        @Override
        public void processElement(Tuple2value, Context context, Collector< ReturnSample > collector) throws Exception {
            if (value.f0 == null){
                return;
            }
    
            Object sampleValue = value.f1;
            Long time = context.timerService().currentProcessingTime();
            ReturnSample returnSample = state.value();
            if (returnSample == null) {
                returnSample = new ReturnSample();
                returnSample.setKey(value.f0);
                returnSample.setTime(time);
                context.timerService().registerProcessingTimeTimer(time +timer);
            }
    
            // 更新点击数据到state里
            if (sampleValue instanceof ClickLog){
                ClickLog clickLog = (ClickLog)values;
                returnSample =(ReturnSample) clickLog.setSample(returnSample);
            }
            state.update(returnSample);
        }
    
        /**
         * @param timestamp
         * @param ctx
         * @param out
         * @throws Exception
         */
        @Override
        public void onTimer(long timestamp, OnTimerContext ctx, Collector< ReturnSample > out) throws Exception {
            ReturnSample value = state.value();
            state.clear();
            out.collect(value);
        }
    }

    ⑥ 拼接后的日志格式化和过滤

    拼接后的数据需要按照在线训练作业的要求对数据做格式化,比如 json、CSV 等格式。
    过滤:决定什么样的数据是合格的样本。例如:有真正阅读的内容才算是可用的样本。

    ⑦ 输出

    样本最终输出到实时的数据队列中。下面是实际的作业拓扑和运行时状态:

    1-2.jpg
    1-3.jpg

    整个样本拼接过程的流程图:

    1-4.jpg

    StateBackend 的选取

    使用 RocksDB/Gemini 作为 state 的 Backend 的优势和建议:

    我们用大数据对 memory 和 RocksDB,Gemini 做了实验对比,结果显示 RocksDB 和 Gemin 在数据处理,作业稳定性和资源使用等方面比 memory 更合理。其中 Gemini 的优势最为明显。

    此外,如果是大数据量的 state,建议使用 Gemini + SSD 固态硬盘。

    样本的监控

    1. Flink 作业的异常监控

    • 作业失败监控
    • Failover 监控
    • Checkpoint 失败的监控
    • RocksDB 使用情况的监控
    • 作业消费 Kafka 的 Comsumer Lag 的监控
    • 作业反压的监控

    2. 样本输入端 Kafka 的消费延迟监控

    3. 样本输出端 Kafka 的写入量的监控

    4. 样本监控

    • 拼接率监控
    • 正样本监控
    • 输出样本格式的监控
    • 输出标签对应的值是否在正常范围
    • 输入标签对应的值是否为 null
    • 输出标签对应的值是否为空

    样本的校验

    样本生成后,如何验证数据是否准确

    1. 在线和离线的相互校验

      将在线样本从输出的 Kafka 中接入到 HDFS 上离线存储。并按照在线 join 的时间窗口来分区。
    2. 用同等条件下生成的离线样本和在线样本做对比
    3. 白名单用户的全流程校验

      将白名单用户的日志和样本结果存入 ES 等实时数仓中,来做校验。
      

    故障的处理

    样本异常对线上模型训练的影响非常大。当发现异常报警时,首先要做的是向在线模型训练作业发送样本异常的报警。收到报警信息后,模型停止更新。从而避免影响模型线上效果。

    普通意义的业务故障解决后,丢弃原来的数据,所有输入日志流从最新的时间点开始消费并生成新的样本即可。重要业务需要重置输入日志流的 Kafka offset 从故障时间点开始重新生成样本数据。

    平台化

    通过平台化对样本生成的流程做出严格的规范非常重要。在平台化的过程中,需要提供简单通用的开发模板以提高作业开发效率;提供平台化的作业监控和样本指标监控框架,避免重复造车;提供通用的样本输出落地策略,和在线/离线校验策略,更便捷的为业务方服务。

    微博基于 Flink 搭建的在线样本生成平台架构,如图:

    1-5.jpg

    UI 页面,如图:

    1-6.jpg
    1-61.jpg

    基于平台化开发,用户只需要关心业务逻辑部分即可。需要用户开发的有:

    1. 对应输入数据的数据清洗逻辑
    2. 样本输出前的数据清洗逻辑

    其余的在 UI 上配置即可实现,具体有:

    1. 输入 Kafka 的配置信息及对应数据清洗的 UDF 类
    2. 样本拼接的时间窗口
    3. 窗口内对字段的聚合操作
    4. 样本输出的 Kafka 配置信息及输出前数据清洗和格式化的 UDF 类

    资源情况由平台方审核并配置。完成后,自动生成并提交作业。

    1-7.jpg

    作业提交后:

    1. 平台会提供如前所述的作业相关监控,如下:

    ■ Flink 作业的异常监控

    作业失败监控
    Failover 监控
    Checkpoint 失败的监控
    RocksDB 使用情况的监控
    作业消费 Kafka 的 Comsumer Lag 的监控
    作业反压的监控

    ■ 样本监控

    拼接率监控
    正样本监控
    输出样本格式的监控
    输出标签对应的值是否在正常范围
    输入标签对应的值是否为 null
    输出标签对应的值是否为空

    2. 平台会自动将数据落盘,存储到HDFS上。方便离线验证或者离线训练。

    3. 用户只需将精力放到样本的验证上即可,由平台方保证作业的稳定性。

    作者介绍:

    曹富强,微博机器学习研发中心-高级系统工程师。现负责微博机器学习平台数据计算/数据存储模块,主要涉及实时计算 Flink、Storm、Spark Streaming,数据存储Kafka、Redis,离线计算 Hive、Spark 等。目前专注于 Flink/Kafka/Redis 在微博机器学习场景的应用,为机器学习提供框架,技术,应用层面的支持。

    ]]>
    【其他】阿里云SCDN改版通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【SCDN】【产品改版通知】

    安全加速SCDN产品将于2020年10月1日起进行产品改版,老版本SCDN将停止续费,阿里云提供了优惠或免费的迁移方案,建议您迁移到新版SCDN。新版SCDN产品详情点此查看。

    新老版本差异:

    老版SCDN主要提供CDN分发+DDoS高防的产品能力,最高提供联通,电信双线300G DDoS防护能力。

    新版SCDN将主要提供安全CDN带宽,独享资源,并提供全网100W QPS,单点6W QPS能力,同时支持频次控制和流量管理等功能。

    老用户迁移方案,详情请参考

    如有任何疑问欢迎加入 SCDN产品改版钉钉群 34125539 咨询

    ]]>
    【其他】9月11日ECS第七代高主频实例公测通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【ECS第七代高主频实例】【公测通知】

    公测时间:2020年9月11日至10月31日

    公测内容:阿里云中国站(www.aliyun.com)ECS将开放新增第七代高主频实例产品公测,首次开放地域包括杭州、广州、乌兰、河源,后续会有更多地域开放。

    七代高主频实例产品是依托第三代神龙架构,采用intel最新一代cooper lake处理器,可以提供3.8Ghz的全核睿频,为用户提供稳定可靠的超高性能。实例的规格名称为hfg7/hfc7/hfr7系列,具体的产品规格参数及使用场景请详见官方文档介绍。购买方式及售卖价格请参考相关售卖地域页面高主频产品。

    ]]>
    【其他】Flink全托管版商业化通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【Flink全托管服务】【商业化通知】

    Flink全托管服务将于2020.9.15号开始结束公测进行商业化收费,为了保证您业务平稳过渡,请各位用户评估业务影响。针对在公测期间申请公测时间过长的用户,我们将免费服务期限统一限制为2020.9.30 23:59:59,特此通知,如有其他问题,请通过工单联系我们

    ]]>
    【其他】9月16日数据湖分析产品更名通知 Fri, 20 Jun 2025 02:20:33 +0800 【阿里云】【数据湖分析】【产品名称变更通知】

     变更时间:2020年9月16日

     变更内容:自2020年9月16日0时起,阿里云产品“数据湖分析”名称正式变更为“云原生数据湖分析”。如有任何问题,可点击联系我们进行咨询反馈

    ]]>
    助力企业数字化转型 | 斑羚在线、环宇数通、乘云科技入选阿里云原生合作伙伴计划 Fri, 20 Jun 2025 02:20:33 +0800 近日,北京三家重量级伙伴“斑羚在线、环宇数通、乘云科技”喜获阿里云正式授牌认证。自从2019年加入“云原生合作计划”以来,在云原生技术方面,双方开展了深度合作,推进企业进行数字化转型,达成生态共赢。而在此之前,这三家企业与阿里云已经建立了深厚的合作基础,在“新基建”的战略背景下,基于阿里云丰富全面的云原生产品、成熟的云计算技术及平台支撑能力,斑羚在线、环宇数通、乘云科技将为华为区域的企业级用户提供快速可靠、个性化的云原生技术解决方案,造福华北区的广大阿里云客户。

    这么优秀的伙伴,是怎样快速发展起来的呢?让我们逐一为大家揭示。

    01 斑羚在线:一站式全方位服务

    北京斑羚在线网络科技有限公司提供基于阿里云的专业软件开发服务,响应及时、服务精细、团队稳定,深耕IT服务行业10余年,是客户认可的“软件开发明星企业”,更是值得依赖的云服务合作伙伴。作为新零售行业的践行者,斑羚在线独立开发运营的校园新零售项目“零食盒子”,已覆盖全国26省市,超300所高校,触达500万人次高校人群,每日消费笔数达4万以上。除新零售行业,直播领域也是近年来斑羚在线的主攻方向,公司先后向用户提供了“LiveMall电商直播” “螃蟹横屏演艺直播”等解决方案,助力企业创建属于自己的直播+购物平台,转型线上逆市增长。

    此次加入阿里云原生合作伙伴计划,斑羚在线将专注于以阿里云产品和先进技术为核心,向客户提供专属定制开发,按需定制,集群部署,以及领先的技术、完备的解决方案、完善的服务及成熟的渠道等一站式全方位服务。

    112.png

    斑羚在线获得“云原生合作计划伙伴”授牌

    02 环宇数通:助力企业轻松不如云时代

    北京环宇数通科技有限公司成立于2009年,核心成员来自于国内顶尖安全厂商与集成商。2013年研发出自主知识产权数通云产品,2015年环宇数通正式进入阿里云生态体系大家庭,通过3年时间的努力奋斗、群策群力共同打造了一支优秀的云计算、大数据服务团队。2016年成为阿里云全国授权服务中心,2018年获得ISO27001国际安全资质,2019年成为国信安全合作伙伴。

    image.png

    基于阿里云原生全面的技术和产品,环宇数通为客户提供电商解决方案、App解决方案、金融解决方案、游戏解决方案。以某互联网金融公司为例,得益于微消息队列 MQTT 的多协议、多语言和多平台的支持能力,目前 MQTT 广泛应用于移动互联网以及物联网领域,覆盖移动直播、车联网、金融支付、智能餐饮、即时聊天等多种应用场景。相比使用HTTP等其他协议,客户使用消息队列RocketMQ + 微消息队列 for MQTT的组成方案能节省至少2人月的开发成本,还能通过这套方案支撑起对于安全和可靠性要求极高的金融级业务场景,并轻松扩展到百万组别的客户端。

    244.png

    环宇数通获得“云原生合作计划伙伴”授牌

    03 乘云科技:成立6年,营收破亿

    乘云科技成立于2014年,是基于阿里云生态的专业的云计算业务供应商,为泛行业客户提供云计算资源转售、云服务技术支持、云产品渠道生态共创以及云业务IT咨询等。成立六年来,发展迅速,2019年营业收入突破一亿元。

    image.png

    乘云总部设立在北京,在沈阳、成都、武汉、深圳分别设有分公司。在河北的邢台设有50多人的外呼中心。乘云的理念就是用普惠的云计算、云服务、服务于全国的云计算用户。针对云上技术服务和上云的技术服务,乘云有一系列的解决方案,基于阿里云完善的基础设施,整合云上PaaS层的调配资源,完善客户的技术架构的同时,协助客户提供优质的云上管理方案。

    image.png

    乘云科技获得“云原生合作伙伴计划”授牌

    乘云作为阿里云优质的服务商,提供全站的上云解决方案,全流程的技术协助、云上便捷的运维管理能力,上云企业的技术培训指导,面对客户新需求的定制化的架构服务,以及面对中大型企业主流的混合云架构的落地都是具备成熟的闭环能力的。

    随着云计算的全面到来,普通的创业者也能拥有和500强企业一样的计算资源。创业公司像飞机,速度够快才能起飞,通过现有的云端资源才能更快的让产品落地。通过阿里云,普通的创业者也能和大企业同台竞争,让创业进入快消时代。

    在企业数字化转型加速的浪潮下,越来越多的企业开始探索云原生架构如何落地。通过云原生,企业可以最大化使用云的能力。阿里云将与合作伙伴携手,通过全面丰富的云原生技术和产品,激发更多企业在云原生时代的生命力。

    点击:云原生合作伙伴计划,了解更多云原生合作伙伴计划细节和加入详情。

    ]]>
    阿里云 SAE 携手云效助力「石家庄掌讯」持续交付、降本提效 Fri, 20 Jun 2025 02:20:33 +0800 背景

    石家庄掌讯信息技术有限公司创立于2009年,是一家提供企业信息化咨询、创新型软件产品、电商代运营服务,标准化管理、快速发展的高新技术企业。当前公司正处于企业互联网市场突破转型重要阶段,希望将更多精力转移到业务创新,提升开发和交付效率,低成本试错。因此选择一套低门槛开箱即用的持续交付、快速部署&运维平台尤为重要。

    面对的挑战

    • 组织、人员权限管理复杂:
      Jenkins的权限管理独立一套,与其它系统不能很好的对接,维护成本非常高。
    • 好的工程实践、流程规范不容易复用,质量更无法保证:
      代码开发过程中加入了阿里巴巴P3C规范,与集成工作流无打通,不得不依赖人工介入的效果,当项目的时间紧,任务重,往往执行不到位,效果无法保证,形同虚设。代码质量很难保证。
    • FTP手工发布效率慢,Jenkins需要编写大量的脚本,降本提升效率成为了瓶颈:
      公司在很长一段时间在使用Jenkins作为自动化部署工具,Jenkins易用的插件化模式和灵活的流水线脚本编写能力是我们一直使用他的原因。不过对于我们这种0运维人员的小团队来说,一些平台细小的不便性也会团队效率带来很大的负面影响。Jenkins的流水线脚本编写维护的繁琐性以及第三方平台(服务)与Jenkins整合的复杂度都给我们带来不小的麻烦。
    • 缺少专职运维人员和微服务改造实战经验,研发运维效率不高:
      公司未设置专职运维人员,在做业务创新同时,微服务架构同步改造进行中。采用自建微服务架构+APM,技术门槛和人力不足很难在短时间成功落地。急切需要一条快速上手的平台支撑,需要最大限度屏蔽底层IaaS, 容器,以及常用微服务套件的学习成本。
    • 测试开发环境和生产环境的闲置计算资源较高:
      长期保有固定的IaaS资源,单台ECS单部署应用,导致资源利用率很低,存在较多的闲置浪费。

    1597802958345-20d3d343-c3f4-49ee-99bd-4805d7a901e6.png

    架构图

    解决效果

    总体上来说,掌讯信息公司在与云效&SAE共建之后,通过Codeup自动化代码检查提升了代码质量,降低了生产的故障,FLow自动化流水线、SAE的接入不但降低了成本,同时提升了交付效率。

    1. 降成本:

    零成本投入提高质量管理能力和持续交付能力
    免费使用的codeup代码库让我们节省了自建git代码库的成本,codeup中集成的代码审查和安全审查模块又让我们节省了质量管理的投入成本。免费的流水线flow让我们节省了原本在jenkins中投入的环境成本。这些还只是节省的有形成本,对于平台整合后效率的提升所节省的成本更是不可估量。

    低成本高质量玩转微服务架构
    对于我们这样的小团队想要玩转微服务架构一直是一个可望不可即的事情,直到发现了阿里云的SAE(Serverless应用引擎)产品,SAE(Serverless应用引擎)节省了自建微服务架构的ECS成本。基于秒级弹性能力,无需长期保有固定资源,按需启停和自动弹性、按分钟计费,极大的提升了资源利用率。使用SAE(Serverless应用引擎)后,公司单从硬件成本上就节省了50%。

    2. 提效率:

    SAE,从0到1的高效体验
    SAE 提供了应用托管和应用监控的开箱即用的体验,帮助我们关注业务开发而非底层资源的运维,节省了大量的人力成本。目前,我们通过 SAE 上线了 5 个核心业务:商品中心、用户中心、销售中心、采购中心和库存中心。

    SAE,0改造使用Serverless技术
    SAE实现了微服务应用的无缝迁移,WAR/JAR无需容器化改造直接部署,这也是SAE区别其它Serveless产品的重要优势,平滑迁移企业在线应用。

    云效codeup代码库,加速你的code review
    在codeup代码库中code review是在每一次commit后自动进行审查,和我们之前的集中时间,集中人力做code review工作相比,分散在commit后的自动审查模式更节约时间,并且自动化的触发模式减少了人为参与产生误判的可能性。

    云效flow,高易用性,高集成性
    使用云效flow产品后很多问题迎刃而解,可视化的流水线配置让之前繁琐的脚本编写工作一去不复返。flow高度集成阿里云产品线,在流水线flow中轻松集成我们在阿里云使用的SAE环境。我们曾尝试让一名有3年工作经验的普通的开发人员在flow中构建一个java测试应用部署的流水线,结果他在没有查看任何文档的情况下很快的完成了流水线的配置工作。这些体验都让我们切身感受到flow产品的高易用性和高集成性。

    3. 提升质量,减少故障:

    一提到流量增长,大家第一时间可能想到的就是加机器加带宽,但往往这个时候加机器已经来不及了。以往我们都是提前预估峰值,按峰值保有ECS资源,但经常出现容量预估不准的情况(比如资源浪费或资源不足),更严重的是会影响系统的SLA。通过SAE的秒级自动弹性,我们可以轻松的动态扩容应对峰值大考,峰谷时按需自动缩容。

    以前团队中的code review工作只能指定专人在合并代码后执行审查任务,并根据生成的审查报告统一对问题进行修订,在使用云效codeup代码库后,代码审查工作被分配在每次commit后自动执行,以前统一的审核时间被分配在团队中每个人commit代码后自动执行,不但减少了人员参与的人力成本而且减少了code review工作执行的时间成本,大大提高了工作效率。由于整个code review工作提前到了代码commit阶段,将发现并解决代码潜在问题的阶段提前到了代码合并之前,这样就减少了在后续环节中出现遗漏问题的可能性,这里不得不提一下codeup审查代码阶段包含的敏感信息和依赖包漏洞扫描功能,这两个在codeup中自带的安全扫描功能,不但调高了代码质量的安全性,而且潜移默化的提高了团队的安全意识。

    对比 原来 现在
    代码规范评审 代码评审环节滞后,人为控制环节多。 commit自动化评审
    代码安全评审 缺失安全评审工作 commit自动化安全评审
    发布方式 Jenkins手工编写流水线脚本 高度可视化操作--简单
    Jenkins通过API方式集成阿里云产品(SAE) 高度集成阿里云产品(SAE)--便捷
    分布式环境建设 购买ECS设备构建环境 SAE无需购买ECS,按资源使用量弹性付费
    分布式环境运维 自建运维环境,集成多种运维工具 SAE提供统一运维平台,高度集成阿里云运维相关产品。

    结语

    企业创新要做的是成长速度快于消费者需求变化的速度,所以「掌讯信息」深感依靠自身摸索固然也可以持续提升能力,但面临的挑战要求能力的提升也必须更快、更好。在寻求提升开发和交付效率,低成本试错中,「掌讯信息」发现阿里经过数年互联网研发实战,已经总结出了一套高效能的研发思想、流程及工具,那就是阿里云SAE+云效。

    持续交付、降本提效是一个很大的主题,一篇文章要讲透是不够的,只是简单分享了我们在这个过程中遇到的几个关键问题。「掌讯信息」在实践中其实都在摸着石头过河。希望大家可以多交流,共同探索互联网交付效率的成功模式。

    最后,感谢 「SAE + 云效」 这两款工具及官方团队给予我们的支持,希望在未来更加深度的合作中,「掌讯信息」和 「SAE + 云效」 都能为更多团队的交付效率提供更多更好的经验。

    作者介绍
    乔亚浩,2009年加入石家庄掌讯信息技术有限公司,从业务开发至产品管理,现任职「掌讯企业智能服务部主管」。先后承担多次技术攻坚及推动技术演进:前端混合开发技术落地,产品后端微服务化改造、服务自动化构建及容器化部署、云效CI/CD落地等。

    ]]>
    SpringCloud 应用在 Kubernetes 上的最佳实践 — 高可用(熔断) Fri, 20 Jun 2025 02:20:33 +0800 前言

    阿里巴巴十多年的双十一,锤炼出来了一套业界领先的高可用技术,有一些已经商业化(云产品 PTS、AHAS),也有的开源了如:Sentinel、ChaosBlade。我们这一系列的高可用章节也主要介绍这方面的内容。今天介绍熔断部分,即开源产品 Sentinel 的核心能力。

    问题定义

    在一个常见的分布式应用中,一个请求先通过终端到达 Gateway,再经过防火墙和网络负载均衡,其中还包括调用下游的其它服务和第三方应用,才能到达前端网络服务;如下图所示。

    1.png

    和这样一个架构一样,大家可能也会遇到如下的一些熟悉的 Case :

    • 瞬间洪峰流量导致系统超出最大负载,load 飙高,系统崩溃导致无法正常提供服务。
    • “黑马”热点数据击穿缓存,DB 被打垮,挤占正常流量。
    • 调用端被不稳定服务拖垮,线程池被占满,导致整个调用链路卡死甚至系统雪崩
    • ......

    这些不稳定的场景可能会导致严重后果。大家可能想问:如何做到均匀平滑的用户访问?如何预防流量过大或服务不稳定带来的影响?这时候我们就要请出微服务稳定性的法宝 —— 高可用流量防护,其中重要的手段就是流量控制和熔断降级,它们是保障整个系统稳定性重要的一环。

    流量控制

    流量是非常随机性的、不可预测的。前一秒可能还风平浪静,后一秒可能就出现流量洪峰了(例如双十一零点的场景)。然而我们系统的容量总是有限的,如果突然而来的流量超过了系统的承受能力,就可能会导致请求处理不过来,堆积的请求处理缓慢,CPU/Load 飙高,最后导致系统崩溃。因此,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量控制。

    2.png

    熔断降级

    一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

    3.png

    Spring Cloud 中如何做熔断?

    在原来的 Spring Cloud 产品族中,有自带的熔断组件 Hystrix ,是 Netflix 公司提供的一个开源的组件,提供了熔断、隔离、降级的这些特性,不过 Hystrix 在 2018 年 11 月份开始,就不再迭代开发,进入维护的模式。不过好消息是也就是这一年开源了 Spring Cloud for Alibaba 产品族,其中的 Sentinel 完美的对 Hystrix 做了补充,下面针对 Sentinel 做一些基本介绍。

    Sentinel 工作原理?

    Sentinel 以资源流量(URL、线程、本地函数、Dubbo服务等)为切入点,根据用户输入的规则,自适应的做到流量控制、熔断降级、系统负载保护等多个维度,全方位的保障系统的稳定性。并提供了一套具备丰富的应用场景、完备的实时监控、广泛的开源生态、完善灵活的 SPI 扩展点的完美的高可用解决方案产品,一个基本的原理介绍图如下,详细介绍请参考官方文档

    4.png

    在使用上,针对主流框架默认提供自动适配的能力来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。同时,Sentinel 也提供开放的接口,方便您自定义并改变规则。

    快速使用 Sentinel 的方式

    除了在开源提供的方案之外,Sentinel 已经以多种形态进入到了各种云产品的组合解决方案中,列举如下:

    一、在 AHAS 中使用

    Sentinel 现在已经是阿里云云产品 AHAS 的重要能力,使用方式请参考官方文档,相比开源的方式接入,云产品主要省去了繁琐的配置,提供了更快的接入方式,以及更友好的产品管控界面,以及更强大的能力;当然除此之外,最重要的是在接入和运行的过程中,都可以获得原厂同学的直接的支持。

    二、在容器服务 Kubernetes 集群中使用

    在容器服务中我们目前做到了纯白屏和云原生的方式进行安装,使用方式请参考官方文档来安装所需的 pilot,在集群中安装完 pilot 之后,会自动选择集群中打上了相应 AHAS 注解的 POD 进行 Sentinel Agent 的挂载,配置如下:

    annotations:
      # 是否开启 AHAS 应用流控插件, on、true 表示开启, off、false表示关闭
      ahasPilotAutoEnable: "on"
      # 服务名称,会显示在 AHAS 控制台上
      ahasAppName: "<your-service-name>"

    三、在 EDAS 中使用

    在 EDAS 中,如果选择的是部署在容器服务K8s集群或 Serverless K8s 集群中的应用支持通过重新部署来接入AHAS,并可在EDAS内嵌的监控页面中实时监控流量规则,所有的配置能力都能通过一个控制台白屏化操作完成,全面可视化地保障您的应用可用性,使用方式可以参考文档

    结尾

    本文简单介绍了高可用流量防护的背景和手段,在熔断的场景下,我们理解十年的积累打磨了高可用产品 AHAS 来为 Kubernetes Spring Cloud 应用保驾护航。除此之外,AHAS 高可用防护还提供以下能力:

    • 针对不稳定弱依赖的熔断降级能力,支持慢调用比例/异常比例策略,支持渐进式恢复策略。
    • 机器维度的系统自适应保护,智能化调配系统流量
    • 全自动托管、高可用的集群流量控制
    • 针对 Nginx 网关及 Spring Cloud Gateway、Zuul 等 API Gateway 的网关流控
    • 针对 Istio/Envoy 集群的 Mesh 高可用防护

      需要注意的是,流控降级的配置是需要结合容量规划、依赖梳理来做的。我们可以借助阿里云 PTS 等压测工具对我们的服务进行全链路压测,了解每个服务的最大承受能力,来确定流控和熔断降级的阈值。同时,业务系统需要具备实时监控的能力,以便实时地根据流量情况做出相应的限流降级策略调整,我们下面的章节中将介绍这一利器。
    ]]>
    闲鱼靠什么支撑起万亿的交易规模?| 云原生Talk Fri, 20 Jun 2025 02:20:33 +0800 image.png
    造梦者 | 王树彬,阿里巴巴闲鱼架构负责人

    2014年6月28日,阿里即将赴美上市的这一年,西溪园区的一个茶水间里,28个人日夜赶工了三个月后,上线了一个闲置交易平台——闲鱼。今年5月份,在阿里巴巴的年报中对外公布了闲鱼的数据:GMV2000亿元,同比增长100%,每天在线卖家数超过3000万人。 闲鱼已经从一个茶水间创业的内部小产品,变成了在C2C领域的领先平台。

    据艾媒数据估计,2020年全年的二手物品交易市场的规模将达到万亿以上。线上交易的繁荣亟需技术架构做相应的调整、演进才能支撑业务的快速发展。闲鱼对于阿里而言,有比营收更重要的意义,那就是创新。创新不只体现在业务模式上,闲鱼的技术架构也在探索最新的方向——向Flutter化、云原生/Serverless化发展。

    2009年,从浙江大学毕业的王树彬,在UT斯康达工作了三年后,加入阿里巴巴。2017年,王树彬首次将Flutter引入到闲鱼,从2018年开始,王树彬带领闲鱼技术团队在下一盘更大的棋:布局Serverless。颠覆性创新往往是从边缘性的地方出现,而向云原生化/Serverless化升级,对于闲鱼是一条全新的路,但趟出了这条路,对于很多做线上交易的公司有着巨大的借鉴意义。

    今天,我们就一起聊聊闲鱼的云原生故事。

    01 为什么要做Serverless?

    闲鱼是依托阿里电商体系的前台型业务,有非常独特的业务特点和用户诉求,在底层依托阿里系统的同时,在表现层和业务层需要探索适合闲鱼的、并且更加快速灵活的研发体系。

    按照传统的开发方式,闲鱼原有的 IT 系统会面临很多痛点,比如:

    1、客户端交互层、服务端业务胶水层、领域层边界划分不清晰,这就导致很小的业务需求就需要整条链路的同学参与,协同成本高,开发调试周期长。

    2、服务端存在巨型应用,研发耦合、发布耦合、运维耦合严重,甚至系统稳定性也受到很大挑战,单个业务问题往往会影响整个应用。

    3、运维成本极高。为了保障业务的稳定性和可用性,阿里对每一个应用上线都有相应的规范和规则。哪怕是一个很小的内部应用,一天可能只有一两个访问量,上线也需要遵守既有的规范,这势必会消耗一些固定资源。单个应用消耗的资源可能很有限,但所有应用消耗的资源累积起来也是一个不小的数字。而对于巨型应用,由于影响面巨大,发布时要有更加严格的流程和步骤,一次发布至少要耗时6小时,导致运维成本极高。

    Serverless 的出现,一方面使云端一体化研发成为可能,很多小业务需求的协同成本可以大大降低。另一方面,Serverless 使业务胶水层的巨型应用,有了比微服务更加合理的拆分方式。

    传统巨型应用的成本(速度)、稳定、质量相互制约的瓶颈,可以用下面这个三角形来直观的表示。
    image.png
    云原生/Serverless 这些新技术的出现,可以使应用运维能力下沉,传统巨型应用的成本(速度)、稳定、质量相互制约的瓶颈才有可能被打破。闲鱼在落地新技术的过程中,先围绕 Flutter 重点攻坚了 Flutter 混合工程体系、高性能组件库。然后围绕Serverless 重点攻坚云端一体化研发体系、服务端业务组装层架构体系。

    闲鱼客户端基于 Flutter 进行架构演进与创新,通过 Flutter 统一 Android 和 iOS 双端提升研发效能之后,希望通过 Flutter+Serverless 解决各角色间存在的大量的协同问题,正是这些问题导致整体研发效率低,移动端离业务越来越远,服务端没有时间做底层领域沉淀。通过 Serverless 的引入,闲鱼会明显看到整体研发效率的提升。

    02 一边探索,一边实践

    2018年,闲鱼技术团队开始探索 Serverless,整体分为四个阶段:自建Dart Server、依托FaaS平台、云端一体化、传统巨型应用Serverless化。

    2018年5月,以 Serverless 思路构建了2s内冷启动的 Dart Server 应用框架,用于服务端业务胶水层的轻量化开发。

    2018年底到2019年初,闲鱼启动与Gaia团队协同共建基于Gaia平台的Dart 运行时,并上线了部分业务。注:Gaia是基于阿里云的面向淘宝业务特点封装的、用于淘宝业务的FaaS平台。

    2019年,闲鱼基于Gaia的Dart Runtime标准化,探索 Flutter+FaaS 云端编程一体化,领域接口元数据化,最终诞生了 Nexus 等胶水层业务框架,并在闲鱼20多个业务落地。

    2020年,闲鱼开始进行云端的工程&工具一体化,目标是实现一个工程、多端部署。现在,王树彬正带着技术团队攻坚业务胶水层的传统巨型应用治理,使传统应用向Serverless化迁移,“最快3个月,最晚6个月,我们就会交出一份漂亮的答卷。”

    具体来看过去这两年的时间里,闲鱼在Serverless上的实践成果,主要分为5个方面:

    1、云端编程模型一体化框架(Nexus API)
    这个框架的目标是使Flutter、FaaS的编程模型统一,打通UI、交互、数据、逻辑。王树彬提到,一开始说要做Flutter + FaaS一体化的时候,我们对“一体化“这三个字的认知相对比较模糊,只是知道端侧的同学可以用 Dart 这门语言来写FaaS函数,这其实还停留在语言上的一体化。对于FaaS所能做的事,也仅仅停留在前端实施已久的BFF层面。

    我们花了很长时间来讨论,基于Dart生态下,前端的 FaaS 在研发交付其实并不高效,研发阶段主要面临的问题是:

    编程语言不统一:编程语言本身虽然不是最大的障碍,但这也确实给前端开发者增加不少门槛,而且更重要的是语言背后的生态、环境与体系更是一道高高的墙。

    开发模式与架构割裂,环境复杂:端侧一个工程,FaaS侧也有一个独立的工程,它们背后有自己的一套构建、调试、集成/发布的工具链;除此之外,FaaS 还有自己配套的环境、Runtime、框架作为支撑。开发者面对这样复杂的 FaaS 研发环境与双重的研发工作流是无法做到高效交付的。

    最终,我们对一体化有了一个比较清晰的共识,那就是要实现两个核心的一体化:

    • 语言一体化
    • 开发模式与架构一体化

    编程语言的一体化可以为开发者提供一种熟悉的技术栈,开发模式与架构一体化能帮助开发者解决工程割裂以及背后复杂的 FaaS 本地运行环境问题,带来与原研发模式基本一致的研发体验。

    通过这两个层面的一体化,最终达到开发 Flutter 页面和开发 FaaS 无明显Gap。例如,闲鱼客户端Flutter以往是用Redux框架开发,在Nexus API框架下,可以使Redux与FaaS调用无缝集成。
    image.png
    2、CLI 开发工具标准化

    云端一体化开发时,通过 CLI(命令行工具)屏蔽 FaaS 开发的一些细节,使客户端开发 FaaS 时的开发体验标准化,符合客户端同学的本地开发习惯。

    image.png

    3、基础服务 BaaS 化

    过去两年,我们在逐渐简化基础服务能力,如对象存储、消息、搜索。同时,建设业务领域层服务的元数据中心,这些简化的基础服务能力,再加上已有的业务领域层服务,使客户端同学可以快速组装业务。

    4、云端工程一体化

    闲鱼在成功引入 Flutter 后,在端侧形成了以 Flutter 为主、H5为辅的跨端研发体系,使传统的 Android 和 iOS 的两端研发,合并成一端。在端上的生产力得到释放时,我们发现端的同学有机会向下层走一点,使服务端面向简单的数据组装逻辑,由端的同学一人闭环完成,这套模式尤其适用于一些小业务的需求。类似的尝试业界其实早就有了,例如 GraphQL 框架的流行,前端的BFF层的形成。但有了Serverless,服务端轻量代码的开发可以极大地简化,所以闲鱼选择这个时机推进云端一体化。
    云端一体化涉及到云端编程框架、工具链、工程体系、基础服务BaaS化、领域服务下沉,同时,也涉及人员上的组织保障、分工重塑、安全生产培训等。

    5、传统巨型应用的Serverless化改造

    Serverless不是银弹,但与业务胶水层的特点很匹配,非常适用于解决胶水层的传统巨型应用的拆分,这也是闲鱼正在攻坚的下一个难题。
    image.png

    03 难题与破局

    闲鱼落地 Serverless 的过程中并非一帆风顺。王树彬提到,在Serverless云端一体化过程中,遇到了一些技术难题,比如Java富客户端的异构语言访问、开放环境如何统一以及客户端同学对领域接口不熟悉等问题。

    在闲鱼的Java系统中,存在大量的Java富客户端应用。针对Java富客户端的异构语言访问,闲鱼以Sidecar的模式,建立Java的Proxy来解决这类问题。

    紧接着,为了让开发环境统一,闲鱼开发了自己的CLI工具(GCLI)。GCLI是一个基于支撑 FaaS 研发生命周期的命令行工具,它定义了闲鱼 FaaS 开发闭环,统一了 FaaS 的研发环境,是提升FaaS研发效率的利器。GCLI 将研发闭环拆解成适合Serverless 研发习惯的开发指令。为了让用户继承其研发习惯和工具,闲鱼优先选择了基于本地的开发方案;使用Docker技术统一开发环境,在 Dcoker 内声明Dart FaaS技术栈依赖的运行环境(软件+配置)。借助容器技术,FaaS 的软件环境可以移植到任何支持Linux运行的操作系统,从而解决了环境统一的问题;GCLI 通过 FaaS Open API 实现本地和函数平台实现互操作,形成完整的研发闭环。

    最后,针对客户端同学对领域接口不熟悉的问题,闲鱼开发了领域层的元数据中心。

    云端一体化重塑了传统的云、端边界,减少了协同,也给人员的分工带来了更大的灵活性,技术上的研发效率、研发质量也明显提升。而这些改变对于业务带来的直接好处,就是可以让业务有更快的迭代速度、更快地适应市场和用户需求的变化。

    云端一体化目前应用在闲鱼的重交互场景以及轻量业务场景中,其带来的技术效率、质量提升更容易以量化的数据形式呈现。例如,以典型的中大型业务需求抽样统计,开发人日降低了30%,千行代码Bug率降低了20%。如果以零散需求统计,数据提升会更加明显。以往的小需求由于多个同学参与,往往排期需要几周,而云端一体化后,资源的灵活性明显提高,使需求响应速度大大提升。
    image.png
    “但是,还有一些问题没有解决”,王树彬说,在 Serverless 的巨型应用拆分方面,闲鱼遇到的问题更加严峻,比如:

    • 微服务和 Serverless 的选型
      在 Functions 之间代码复用

    对函数的依赖做统一升级

    这几个问题的方案,闲鱼还在逐步验证中,待经验成熟后再向大家详细分享,欢迎持续关注。

    04 借鉴与思考

    什么样的公司、应用或场景应该选用 Serverless 的架构模式?目前没有具体的定义,关键在于想清楚。想清楚,就需要平衡好收益、成本、效率和应对市场的能力。其中,成本是企业更为关注的因素,这其中包括基础设施搭建的成本、运维成本、扩容成本、安全成本等。

    Netflix是落地 Serverless 的一个成功的典型,Netflix 在产品设计上一直都有创新的基因,除了不间断的 A/B 测试之外,每周都会发布很多新功能。为了确保这样高强度的工作成果,就需要一个 API 服务平台来帮助客户端工程师快速而有效地将更改的需求部署到服务层。FaaS 通过把那些与服务相关的所有平台组件抽象为业务逻辑本身来实现这一目标,而 Serverless 模式能够为Netflix提供一个平台,即使没有服务器和运营经验的工程师也可以开发高可用的服务。

    采用 FaaS 模式,本质上是对交易速度和可能性的定制化。有些应用程序的 FaaS 服务表现得很好——Netflix API 的情况就是如此,Netflix 运行的是相对统一的微服务,只需要访问和改变下游服务的数据。然而,如果服务需要定制化,例如需要改变服务平台的各个组成部分,像 RPC、数据访问、缓存、认证等,那么 FaaS 模式可能无法为这些服务提供足够的灵活性。

    自建 Serverless 平台对企业IT人员的要求比较高,同时建设成本也很高。另外,实施Serverless 需要一个成熟的生态。绝大多数情况下,已经上云的企业应该优先考虑云厂商的Serverless产品,而没有上云的企业,需要考虑现有系统的生态情况是否能与云厂商的Serverless产品兼容。

    对于 Serverless 产品的选型,应该综合几个方面来看:生态的成熟度,支持的开发语言,功能丰富度,收费标准等,关键是结合企业自身业务发展的需求。

    05 关于未来

    O'Reilly 曾对 Serverless 的应用情况进行了过一次调查,发现软件行业的开发者关注和应用 Serverless 非常多,这在意料之中,但是金融和银行业也在高度关注Serverless,原因之一是越来越多的金融科技初创企业的诞生,它们承担了传统基础架构的责任,并且以更开放的心态,接纳和拥抱 Serverless 。

    对于拒绝 Serverless 的理由,60% 的受访者表示是安全问题。因为很多行业对于 IT 环境的安全性要求很高,而采用任何新技术都可能会带来安全风险。

    此外,开发者另外一层顾虑主要是担心被厂商绑定,这就导致具备一定规模的组织会基于开源方案,如 Knative,搭建自己的 Serverless 平台。而一旦某个开源方案成为主流,云厂商就会主动去兼容开源标准并增大社区投入。

    Serverless 除了对技术和业务产生影响外,对于企业组织架构和技术人员也提出了新的要求。

    首先,Serverless 改变了沟通结构。按照康威定律,组织架构需要适应新的沟通结构,才是最好的匹配。闲鱼以前负责客户端和服务端的同学是分开的,在全新的 Flutter+Serverless 的背景下,组织结构也需要做相应的调整。经过讨论,闲鱼最终决定按照业务线划分,将客户端、服务端的同学按业务线重新组合到一起。

    其次,Serverless 使客户端的同学有机会更多的了解业务,这就要求客户端同学更加具有业务敏感度。Serverless 促使客户端同学扩大了技术边界,也需要了解一定的服务端开发概念。

    最后,Serverless 要求原有的服务端同学有更好的数据建模、领域建模能力,从而有助于底层接口复用度更好。

    从最开始不被外界看好,甚至被调侃为“咸鱼”,到如今实现了千万DAU,盘活了一个万亿级市场,闲鱼的出现,无论是对前端的电商生态,还是用户在互联网上的生活形式,都产生了重要的影响。

    为了支撑起闲鱼万亿的交易规模,王树彬和技术团队正在紧锣密鼓地进行传统巨型应用的 Serverless 化改造,“闯过了 Serverless 的这一关,才是我比较满意的状态。”

    云栖大会预告:

    在9月17-18日云栖大会上,王树彬将在「Serverless分论坛 — 2020 Serverless 新浪潮」分享《闲鱼Serverless架构实践》的话题,敬请期待。

    ]]>
    阿里云原生十年磨剑:让企业在数字经济时代焕发生命力 Fri, 20 Jun 2025 02:20:33 +0800 15.jpg

    日前,国际知名咨询机构 Gartner 发布了最新云厂商产品评估报告,作为亚洲唯一入选的云厂商,阿里云在计算大类中,以 92.3% 的高得分率拿下全球第一,并且刷新了该项目的历史最佳成绩。本次报告,Gartner 更多关注云原生领域,比如在软件基础设施层面,Gartner 重点评测了中间件等领域,阿里云得分位列全球第二。

    以计算项为例,本次测试选取了 Large-scale provisioning of VMs、Scheduled autoscaling、Workload migration service 等 33 条评估细项,涉及容器服务能力(ACK)在内的多项领域,在必备能力和推荐能力的评估项目中,阿里云全部拿到满分,并获得分析师的一致好评。

    在软件基础设施服务领域的 21 条评估细项中,涵盖各类数据库服务、服务网格 ASM 、 FaaS 、消息队列等领域,阿里云得分率 82.2% ,位居第二。同计算、存储一样,数据库与中间件产品能力也是分析师认可阿里云的优势领域。

    从 2011 年率先在国内布局容器技术开始,阿里在云原生领域已经深耕了十余年。在这期间涌现了众多云原生技术和产品,并在开源领域贡献了多款深受开发者欢迎的开源项目,如 Dubbo、RocketMQ、Sentinel、Spring Cloud Alibaba、OpenYurt 等。

    在 2020 阿里云线上峰会上,阿里云智能总裁张建锋表示,阿里云将做深基础,做厚中台,做强生态,有信心真正做好数字经济时代的基础设施。经实践证明,云原生是企业实现数字化转型的最短路径。

    云原生产品全面升级

    目前,阿里云拥有国内最丰富的云原生产品家族,覆盖八大类别 20 余款产品,涵盖底层基础设施、数据智能、分布式应用等,可以满足不同行业场景的需求。同时,阿里拥有最全面的云原生开源贡献。截至目前,阿里拥有 400 多个开源项目,3600 多位贡献者参与了阿里开源项目,在开源社区获得 30 多万个 Star。
    image.png
    除了支持集团内部应用规模化运维,阿里云云原生技术还向全社会输出。阿里云拥有国内最大的容器集群和客户群体,其容器服务(ACK)已在中国及海外 19 个公有云可用区开服,同时也支持客户在自有机房和边缘端的部署使用 Kubernetes。同时,阿里云还提供了丰富的差异化产品:兼容 Istio 的托管版服务网格、基于弹性容器实例的无服务器 Kubernetes(ASK)、提供镜像扫描的独享版容器镜像服务 (ACR),还有基于轻量虚拟机技术的安全沙箱容器运行时和托管服务网格(ASM),它是业内首个全托管Istio兼容的服务网格产品,为容器化的微服务应用提供一致的流量控制和观测能力。

    在中间件领域,阿里云原生拥有全球最大规模的软负载集群,消息处理量日均万亿条,双十一峰值可以达到亿级 TPS ;RPC 调用量日均万亿次,双十一峰值可以达到亿级 QPS 。通过阿里云全面的云原生产品,企业相当于站在巨人的肩膀上,即使是普通的中小企业业务系统也能具备阿里电商的敏捷、弹性、稳定性。
    image.png
    过去十年,云逐步向 Serverless 演进。2016 年阿里云发布的函数计算提供了函数级抽象,2019 年发布的 SAE 提供了应用级抽象,这些产品都抹去了服务器的概念,让用云方式得到极大的简化,并逐渐成为趋势。阿里巴巴不仅在淘宝、支付宝、钉钉、闲鱼上将 Serverless 应用于生产,新浪微博、石墨文档、跟谁学、Timing 等企业也通过阿里云 Serverless 产品,免去了维护复杂机器状态的工作,大幅降低了 IT 成本。

    云原生服务升级,从服务技术到服务业务

    阿里云拥有国内最大规模的云原生应用实践。疫情驱动企业线下业务转到线上,架构互联网化渐成趋势。全链路压测、极速弹性扩缩容以及云原生的全栈技术已广泛服务于互联网、金融、零售、制造、政务等领域企业和机构,大幅降低了应用开发的门槛,加速企业数字化转型的进程。

    利用 PTS 压测,实现资源规划可视化

    根据 Amazon 统计,每慢 100 毫秒,交易额下降 1% 。这些事件和统计数据为大家敲响了警钟,也说明了性能压测对于企业应用的重要性。性能测试服务 PTS 是一个 SaaS 性能测试平台,提供场景 API 编排功能。结合阿里巴巴的自研平台和引擎,支持按需设定压测模式、压测量级、压测时间,快速发起压测,监控压测过程并生成报告等功能,同时也兼容开源工具 JMeter 。

    作为电商行业的独角兽,完美日记通过性能测试服务 PTS 和应用高可用服务 AHAS,第一次参加双十一就成为 2019 年美妆行业第一,第一个破亿。通过大促之前反复压测,完美日记把可能出现的问题都在大促前暴露出来,大大小小提前发现并解决了20多个问题,最终把不确定性变成了确定性。

    快速扩容,容器服务秒级扩容千个 Pod 的能力,平滑应对突发流量高峰

    阿里云是连续两年国内唯一进入 Gartner 《公有云容器服务竞争格局》报告的云厂商;在 Forrester 首个企业级公共云容器平台报告中,阿里云容器服务位列Strong Performer ,中国第一。

    容器镜像服务可自动执行并优化基本镜像分发流程,支持通过 P2P 大规模分发到 1 万个节点,效率高达以前的 4 倍。企业可在数分钟内扩展到 1000 个节点,而阿里云 Serverless Kubernetes 版 (ASK) 和弹性容器实例 ECI 可在 30 秒内启动 500 个容器组。

    新一代容器服务 ACK,可以将最新神龙弹性裸金属实例的强大性能发挥得淋漓尽致,具备极致性能、高效调度、全面安全的特点:

    • 新一代神龙架构具备业界第一的 I/O 转发能力,提供最高 100G 网络带宽;阿里云高速 Terway 容器网络通过网卡直通和数据平面加速,延迟下降 30%。
      第 7 代实例最大支持 192 个 vCPU。ACK 智能 CPU 调度可以轻松释放强大算力,无需应用调整可以实现 QPS 20~30% 提升;结合 ENI 网卡密度提升,可以缩减 50% 的计算成本。

    弹性裸金属实例支持阿里云安全容器,提升端到端安全隔离能力,与开源方案相比性能提升 30%。也支持阿里云首发机密计算容器,基于软硬一体技术有效保护数据隐私。

    云原生正在打通数字化落地的“最后一公里”。疫情期间,阿里云 2 小时内支撑了复工第一天钉钉业务 1 万台云主机的扩容需求。基于云服务器和容器化的应用部署方案,让应用发布扩容效率大大提升,为全国用户提供线上工作的流畅体验。

    面对指数级增长的流量,希沃课堂通过容器服务 ACK 高效管理神龙裸金属服务器和 Serverless 弹性容器实例,顺利积累超过 30 万教师开设 200 万节课程,助力希沃课堂整体业务性能提升 30%,运维成本降低 50%。

    基于阿里云边缘容器服务 ACK@Edge 底座,盒马全程保障疫情期间居民日常供应。结合了云原生技术体系良好的资源调度和应用管理能力,与边缘计算就近访问,实时处理的优势,轻松实现全方位的降本提效,门店计算资源成本节省 50%,新店开服效率提升 70%。

    云原生中间件助力应用架构向互联网化演进

    应用架构向互联网化演进是保障业务稳定和持续创新的根本,应用容器化、微服务化、异步化是实现架构演进的三步曲。应用容器化:通过将应用和环境打包,以容器化的方式实现快速部署,快速交付。微服务化:将一个单体多模块的大应用分解为具有一些原子业务能力的微服务,通过微服务化实现业务快速迭代和变更上线。异步化:引入消息队列中间件,将应用之间的通信异步化,提升应用系统的可用性,减少响应时间。

    云上消息的准确和不丢失对于业务可靠和稳定至关重要。以出行领域为例,如果数据丢失,不仅会导致运行轨迹出现错误,后续应用在自动驾驶上还会带来安全的问题。汽车产业互联网平台大搜车通过云上消息队列 Kafka 的优化版本减少了数据的丢失情况,保证了业务的可靠性和稳定性。

    Timing App是一款提供在线学习的教育社交类应用,目前已有用户700万人。该企业无专职运维人员,之前采用单体PHP架构,无法满足快速增长的业务需求。通过阿里云Serverless应用引擎 SAE 来管理底层 IaaS 资源,解决了客户长期以来代码耦合度高、运维复杂、开发迭代效率低、资源利用率不高等问题。

    EDAS 3.0 无侵入构建云原生应用,提供更多维度自动监控

    EDAS3.0 除了在微服务治理和容器纳管上提供了差异化的产品竞争力外,还将阿里巴巴应用应用安全三板斧,即可观测、可灰度、可回滚融合其中,并实现了 K8s 集群的监管控一体化,提供了更多维度的自动监控、智能诊断和报告输出等功能。中国邮政、安利、福特汽车、红岭创投等来自政企、新零售、制造、新金融等行业的客户正通过 EDAS 来构建云上的容器应用。

    为提振消费,刺激经济快速复苏,成都市政府通过“消费券”项目,第一次活动就吸引 2000 多万市民报名。为支撑大流量高并发的报名场景,在与阿里云技术专家深入讨论后,成都信通决定采用阿里云企业级分布式应用服务 EDAS 。EDAS 的动态扩缩容、灰度发布等能力,为成都信通提供了一整套的应用生命周期的管理,真正做到一键部署、一键发布。同时 EDAS 平台对应用快速扩缩容以及对流量控制、熔断、降级等功能的支持,很好地帮助成都信通在面对大流量、高并发场景时,业务系统依旧能够持续平稳的运行。

    十年磨一剑!面向未来,阿里云将继续与企业并肩前行,激发更多企业在数字化转型浪潮下的生命力,迎接云原生的下一个十年。

    9月18日云栖大会《企业云原生实践》分论坛,敬请期待。

    升级.jpg

    ]]>
    云原生:重新定义信息产业生态体系 Fri, 20 Jun 2025 02:20:33 +0800

    作者:宁晓民(灭道),阿里云原生生态负责人

    信息产业竞争的核心是技术生态体系的竞争

    半个世纪以来,信息产业的生态竞争从微型机、服务器到PC互联网,到移动互联网,再到云计算时代,以操作系统为核心的产业生态系统的竞争愈演愈烈。

    1.png

    1、基于Wintel体系的计算机产业生态
    在PC时代,以微软和Intel推动软硬件功能的深度适配,协同创新和持续升级,Wintel体系以操作系统为核心,构建了PC计算机软硬件的生态体系,形成了数百个各类基于Windows的软件开发工具,在全球范围内建立了上千万名研发人员参与的开发者社区,每年培训了数以亿计的各类应用软件开发人员,基于Window的各类应用软件数以百万计,拥有超过10亿以上的用户。Wintel体系通过整合软硬件,开发者,软件商,用户等资源,形成了全球个人计算机市场难以撼动的产业生态

    2、基于Android/iOS体系的智能设备产业生态
    从苹果公司推出IPhone智能手机为标志,代表着从原来的PC互联网时代进入到了移动互联网时代,从而全球移动智能设备形成了以Android/iOS为核心的产业生态。苹果公司以软硬件结合为重点,以iOS操作系统为纽带,构建起以“CPU(ARM)+操作系统+开发工具+应用商店+各类应用”为核心的产业生态。同样Google公司以开源为手段,构建与之相配的Android体系产业生态

    3、基于云原生(Cloud Native)体系的云计算产业生态
    从2006年第一次提出“云计算”的概念起,云计算、大数据、物联网、人工智能等相关的技术及产业发展势如破竹,不断渗透当代信息产业,从而实现信息产业升级。应用迁云、上云的过程越来越快,从原来的云托管(Cloud Hosting)到云原生(Cloud Native),生于云长于云,最大化的运用云的能力,从而最大化的释放云计算的技术红利。以容器、微服务、服务网格、不可变基础设施及声明式API等技术为主的云原生技术,能够实现应用系统与基础设施解耦,从而让开发者聚焦于业务而不是底层基础设施,云原生进而成为云计算时代的新“操作系统”。以云原生技术为核心,构建起以“云厂商+异构软硬件+云边端+Serverless化+软件全生命周期+开发者+企业客户”为核心的新一代信息产业生态。

    云原生是释放云计算技术红利的最短路径

    2013年一个名叫“Docker”的开源项目发布,以“应用封装+容器镜像”,直接将一个应用运行所需的完整环境,实现了“一次发布,随处运行”,彻底解决了PaaS用户一致性的问题,进而通过Kubernetes开源项目,采用了一整套容器化设计模式和对应的控制模型,从而明确了如何以容器为核心构建真正能够跟开发者对接起来的应用交付和开发范式。容器+Kubernetes技术的逐步成熟与发展,以“云原生(Cloud Native)”为关键词的技术生态雏形基本确立。
    经过6~7年的技术发展,云原生的概念逐渐被广大的客户和合作伙伴所熟知,云原生技术、云原生产品、云原生架构的概念逐步定义出来。
    云原生技术:让系统更加弹性可靠容错、松耦合、易管理、可观察;代表技术是容器、微服务、服务网格、不可变基础设施和声明式API。
    云原生产品:云计算平台提供的数据库、大数据、中间件、函数技术、容器服务等开放标准的原生产品服务。
    云原生架构:生于云长于云,最大化运用云的能力,依赖云产品构建的IT架构,让开发者聚焦于业务而不是底层技术。
    生产力决定生产关系,以云原生为代表的先进生产力,改变整个信息产业格局,从而重新定义新的信息产业生态。

    2.png

    (1)云原生会成为云计算的新界面

    以容器、Kubernetes技术为主,向下封装底层基础设施差异性,如异构环境,异构硬件,向上支撑多样性的工作负载,如新型计算等,覆盖云、边、端,赋能无边界计算、分布式云,云原生逐步成为云计算的新界面,新一代的操作系统。

    (2)云原生重塑软件的全生命周期

    云原生通过底层基础设施与应用的解耦,在软件研发、交付、运维的全生命周期层面的效率提升,从而对软件行业上下游产业链都会带来变革。在微服务领域,在应对系统复杂性的同时,对可观测性、易测试、环境适应性的层面实现更大解耦,让开发人员聚焦于业务开发。在Mesh化层面,实现网络和流量下沉基础设施,方便软件基础设施和业务解耦,独立演进,实现全链路精准流量控制和资源动态隔离,从而带来效率的提升。以全托管、免运维、极致弹性、按需部署、按需计费、强安全为特点的Serverless无服务器架构也推动着软件研发运维模式重大升级

    (3)云原生加速信息产业转型升级

    随着云原生应用的越来越多,软件厂商从基础设施的资源需求,向精细化管理、更优成本、极致弹性、以及研发效能、交付优化的全生命周期的转化。而底层基础设施的变革,带来的“降维打击”,从而推动整个信息产业的重构。从ISV(独立软件提供商)的软件全生命周期,到硬件厂商、云厂商、ISV、企业客户之间的新一轮的软硬件的供需体系,再到云计算技术、社区、ISV、开发者之间的技术互动体系中,云原生技术作为新一代云技术操作系统,加速推动整个信息产业的快速升级。

    云原生合作伙伴计划是阿里云原生生态体系的重要载体

    “开放、被集成、共赢”是阿里云的一贯追求,今年阿里云智能总裁行癫升级了阿里云公司战略“做深基础、做厚中台、做强生态”,生态建设成为阿里云战略的重之之重。在6月份阿里云生态大会上,阿里云智能基础产品事业部高级研究员蒋江伟宣布阿里云启动“云原生合作伙伴计划“,重点扶持100个头部伙伴,赋能10000家合作伙伴,50万开发者,帮助伙伴云原生技术升级,助力企业数字化转型。

    信息产业竞争的核心是技术生态体系,从以Wintel体系的PC时代到Android/iOS的移动互联网,再到云原生体系云计算时代,对于企业和伙伴来讲,抓住技术发展趋势,提前布局是企业长盛不衰的根本。

    “阿里云原生合作伙伴计划”是阿里云原生生态体系的重要载体,生态竞争的核心。“阿里云原生合作伙伴计划”具有合作模式多样化、合作对象强强化、合作范围立体化的特点,采用“集成/被集成”的方法,从而帮助阿里云生态伙伴优化资源配置,降低交易费用,实现规模化经济。
    3.png
    “阿里云原生合作伙伴计划”主要是从市场合作、产研合作、产业链合作、技术标准4个维度,采用多维度、松耦合、立体式的合作模式,助力阿里云原生伙伴销售能力、产品/解决方案能力、服务能力的全方位能力成长。

    (1)市场合作

    阿里云原生合作伙伴计划,在传统电销、分销(代理、reseller、总代、虚商)的基础上,发展解决方案伙伴,以产品和解决方案集成的方式进行产品销售。同时在商机、品牌等市场合作之上,帮助伙伴从原来线上线下拜客模式,走向产品和解决方案推广模式,在以云原生体系为核心的云计算生态中,助力伙伴实现向高附加值的产品型公司进行转型,帮助伙伴成长与成功。

    (2)产研合作

    阿里云原生合作伙伴计划,以集成/被集成为手段,实现产品双向互动,帮助伙伴与阿里云各自产品线布局。在以云原生体系为核心的云计算生态体系中,采用OEM、OBM、ODM等方式共创、共建新产品,实现三方伙伴产品一方化,通过阿里云直销、云市场、生态等多渠道,帮助伙伴产品推广,实现更大的规模经济效益。

    (3)产业链合作

    阿里云原生合作伙伴计划,以云原生产品售前、售中、售后的全链路,以产品研发、测试、交付的全周期,全面和伙伴进行服务合作,通过培训赋能,实现服务伙伴云原生能力认定,通过能力中心、交付伙伴、外包(委外)等方式进行产品、服务的合作。

    (4)技术标准

    技术是第一生产力,以云原生体系为核心的云计算生态体系,技术发展与成熟是根本。当前云原生技术发展趋势是,以容器、Kubernetes为核心的云原生技术逐渐稳定与成熟,后期将发展为以服务治理、云边端一体化、Serverless等上层技术栈为创新发展的核心。阿里云原生合作伙伴计划,愿和业界同行一起在国际、国家、行业技术标准,以及一些自组织产业联盟共建、共同定义一些技术标准,共同发展云原生生态体系。一个典型的案例就是在2019年,阿里云和微软共同发布全球首个云原生应用标准定义与架构模型OAM,它是一个专注于描述应用的标准规范。有了这个规范,应用描述就可以彻底与基础设施部署和管理应用的细节分开。

    回顾信息产业的历次变革,每次都伴随着新技术的发展,进而推动整个生态体系的再平衡而形成的。从2013年Docker开源、容器技术快速发展开始,2014年Kubernetes开源项目大幅度提高了调度和资源管理能力。无数实践已经证明,云原生成为了云计算的新一代操作系统,以云原生体系为核心的新的信息产业生态已经形成。

    阿里云原生助力企业数字化转型

    随着对云原生技术的探索、实践和积累,阿里云原生形成了业界“四个最”:阿里云拥有国内最丰富的云原生产品家族,最全面的云原生开源贡献,最大规模的云原生应用实践,最大的容器集群和客户群体,致力于帮助客户最大化利用云的价值。

    2019年、2020年阿里云容器服务两次成为国内唯一入选Gartner公共云容器报告,“与去年相比,阿里云在产品丰富度上更进一步,与AWS并列成为全球容器产品最完善的云服务厂商。”2019年全球知名市场调研机构 Forrester 发布首个企业级公共云容器平台报告。报告显示:阿里云容器服务创造了中国企业最好成绩,与谷歌云位于同一水平线,进入强劲表现者象限。“阿里云容器服务提供了广泛的开发和应用服务支持能力,并且具备丰富的市场生态和合作伙伴体系,是企业在中国寻求最完备云服务能力的首要选择。”

    据IDC报告,全球前1000的大企业中,67%的企业已将数字化转型变成企业级战略,企业数字化转型也正成为许多中国企业的核心战略。随着企业上云成为业界趋势,全面使用开源技术和云产品构建软件服务的时代已经到来。如何更好地拥抱云计算、拥抱云原生、用技术加速创新,将成为企业数字化转型升级成功的关键。
    云时代下,企业需要新技术架构,使之更好地利用云计算优势,让业务更敏捷、成本更低、可伸缩性更强。而云原生架构的应用意义正在于此。数据显示,2020 年,超过 50% 的全球组织在生产环境中运行容器化应用程序,到 2022 年将超过 75% 。云原生正逐步成为企业数字化转型的“最短路径”。

    阿里云根据自身积累多年的云原生技术、产品和上云实践,提出完整云原生架构的设计原则、解决方案以及最佳实践,帮助企业找到数字化转型“最短路径”,完成从“压迫感”到“掌控感”的主被动力量转变,加速实现 IT 能力提升,打好降本增效组合拳。

    阿里云坚信以云原生为核心的新一代操作系统,会成为云计算时代新界面,会重塑软件行业的全生命周期,推动信息产业的转型升级。阿里云原生生态体系是云计算、大数据、物联网、人工智能的信息产业竞争的核心。“万物生长,合作共赢”是阿里云原生生态的愿景,帮助伙伴成长是阿里云原生生态的使命,阿里云愿意和广大伙伴一起,在新的信息产业生态中,互利共赢,共同成长!

    ]]>
    Spring Cloud 应用在 Kubernetes 上的最佳实践 — 高可用(混沌工程) Fri, 20 Jun 2025 02:20:33 +0800 前言

    从上篇开始,我们进入到了高可用的章节,上篇提到的熔断能力,是历年保障大促当天晚上整个系统不被洪峰流量打垮的法宝,本篇介绍的措施与熔断有不一样的地方,一个是线上洪峰来临时的保护措施,他更多的是流量低峰或者在专门的演练环境中,针对可能遇见的各类故障,采取演练的手段,来窥探对业务的影响。他的主要目的是让我们自己更加了解自己业务系统的薄弱环节,以便来对症下药增强系统的高可用能力。本文重点介绍为什么要做混沌工程以及如何使用 ChaosBlade 工具和 AHAS 平台快速实施混沌工程。

    为什么需要混沌工程

    任何一个系统都会有未曾可知的故障出现,拿现代工艺已经很好的磁盘来说,有统计数据的磁盘最低的年故障率都可达到 0.39% 。即便是这么底层基础设施,也会有这么高的不确定性。尤其当下大部分的服务形态都是分布式架构,在分布式系统架构下,服务间的依赖日益复杂,更很难评估单个服务故障对整个系统的影响;并且请求链路长,监控告警的不完善导致发现问题、定位问题难度增大;同时业务和技术迭代快,如何持续保障系统的稳定性和高可用性受到很大的挑战。

    云原生系统挑战更大

    谈到云原生,可以说云原生是一个理念,主要包含的技术有云设施、容器、微服务、服务网格、Serverless等技术。云设施指公有云、专有云和混合云等,是云原生系统的基础设施,基础实施的故障可能对整个上层业务系统造成很大影响,所以说云设施的稳定性是非常重要的。
    容器服务的挑战可以分两大类,一类是面向 k8s 服务提供商,服务是否稳定,另一类是面向用户,配置的扩缩容规则是否有效,实现的 CRD 是否正确,容器编排是否合理等问题。
    分布式服务的挑战主要是复杂性,单个服务的故障很难判断对整个系统的影响;service mesh,sidecar 的服务路由、负载均衡等功能的有效性,还有 sidecar 容器本身的可用性。
    一些新兴的部署模式的挑战 如 serverless,现在基本上都是函数加事件的形式,资源调度是否有效,而且 serverless 服务提供商屏蔽了一些中间件,你能掌控的是函数这些服务,那么你可以通过混沌工程去验证你函数调用的一些配置,比如超时配置,还有相关的一些降级策略,这些是否合理。
    以上技术都有相同的共性,比如弹性可扩展、松耦合、容错性高、还有一些易于管理,便于观察这些特性。所以说在云原生时代,通过混沌工程可以更有效的推进系统的“云原生”化。

    每个职位都需要懂混沌工程

    混沌工程是一种思想,他让系统中的每个参与者都学会去考虑一件事情:如果所依赖的某服务中断了服务该怎么办?对于以下四类人群而言,意义尤显突出:

    • 对于架构师来说,可以验证系统架构的容错能力,我们需要面向失败设计的系统,混沌工程的思想就是践行这一原则的方式。
    • 对于开发和运维,可以提高故障的应急效率,实现故障告警、定位、恢复的有效和高效性。
    • 对于测试来说,可以弥补传统测试方法留下的空白,之前的测试方法基本上是从用户的角度去做,而混沌工程是从系统的角度进行测试,降低故障复发率。
    • 对于产品和设计,通过混沌事件查看产品的表现,提升客户使用体验。所以说混沌工程面向的不仅仅是开发、测试,拥有最好的客户体验是每个人的目标 所以实施混沌工程,可以提早发现生产环境上的问题,并且可以以战养战,提升故障应急效率和可以使用体验,逐渐建设高可用的韧性系统。



    混沌工程实操

    在一次完整的演练流程中,需要先做好计划,对相关的演练计划有一个行为预期;演练相关计划的同时,我们推荐的最佳实践是需要配合有业务的自动化测试,每演练一次需要全方位的跑完自动化测试用例,这样才能全面的了解真正的业务产生时对业务造成的影响:
    1.png

    在上面的图中描述了一次完整的故障演练需要经过的步骤,其中的最重要的一步的实践是如何“执行预制混沌实验”?因为这一步需要一个专业的工具,在业内目前最流行的工具是 Netflix 的 Chaos Monkey 和阿里巴巴开源的 ChaosBlade ,我们接下来主要是介绍如何使用 ChaosBlade 来完成一次演练。

    使用 ChaosBlade 去做

    ChaosBlade 是阿里巴巴一款遵循混沌实验模型的混沌实验执行工具,具有场景丰富度高,简单易用等特点,而且扩展场景也特别方便,开源不久就被加入到 CNCF Landspace 中,成为主流的一款混沌工具。目前包含的场景有基础资源、应用服务、容器服务、云资源等。ChaosBlade 下载解压即用,可以通过执行 blade 命令来执行云原生下微服务的演练场景,下面是模拟 Kubernetes 下微服务中数据库调用延迟故障。
    2.jpeg

    使用 AHAS 故障演练平台去做

    AHAS 故障演练平台是阿里云对外部用户开放的云产品,使用方式可参考官方文档。其底层的故障注入能力大部分来源于 ChaosBlade 实现,另一部分使用自身小程序扩展实现。AHAS 相比于 ChaosBlade,除了简单易用的白屏操作之外,还实现了上层的演练编排、权限控制、场景管理等,而且还针对微服务新增应用维度演练,简化演练成本,优化演练体验。

    3.png

    结尾

    混沌工程是一种主动防御的稳定性手段,体现的是反脆弱的思想,实施混沌工程不能只是把故障制造出来,需要有明确的驱动目标。我们要选择合适的工具和平台,控制演练风险,实现常态化演练。阿里巴巴内部从最早引入混沌工程解决微服务的依赖问题,到业务服务、云服务稳态验证,进一步升级到公共云、专有云的业务连续性保障,以及在验证云原生系统的稳定性等方面积累了比较丰富的场景和实践经验;这一些经验沉淀我们都通过开源产品以及云产品 AHAS 一一对外输出。

    ]]>
    【升级】9月1日阿里云华南1、华南2地域网络升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【网络】【升级通知】
    升级窗口:北京时间2020年9月1日 00:00-01:00
    升级内容:由于华南地区运营商相关服务存在一定风险,可能导致网络拥塞,为预防对云上客户产生影响,阿里云计划在9月1日凌晨对华南地域网络做预防性调整,将部分移动流量调整至华东出口。
    升级影响:升级过程中通过南方部分省份移动线路访问阿里云华南1(深圳)、华南2(河源)地域的云资源可能会出现1次不超过30s的间断性网络丢包,应用重连即可恢复正常。升级后,大部分省份延时不会有变化,南方部分省份(如四川、福建)移动用户访问阿里云华南1(深圳)、华南2(河源)地域的云资源延时会有部分增加。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【升级】8月27日至9月2日Centralnic注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Centralnic注册局维护通知】

    维护时间:北京时间2020年8月27日 - 9月2日 

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行不间断地维护升级。

    维护影响:届时.xyz/.art/.love/.icu/.site/.online/.website/.host/.store/.fun/.press/.space/.tech/.ink/.wiki/.design域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月30日.COM/.NET注册局系统维护公告 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【注册局维护通知】

    维护时间:北京时间2020年8月30日 09:00 - 09:45

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .com/.net 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】9月1日消息队列AMQP升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列AMQP】【升级通知】

    升级窗口:北京时间2020年9月1日15:00 - 18:00

    升级内容:华北5(呼和浩特)、华北2(北京)、华东1(杭州)、华东2(上海)、华南1(深圳)、香港、青岛等地域的服务升级。

    升级影响:升级期间消息队列AMQP相关服务访问可能会出现多次闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过 5 分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【升级】9月消息队列MQ升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列MQ】【升级通知】

    升级窗口:

    北京时间2020年8月31日 22:00 - 2020年9月1日 04:00

    北京时间2020年9月3日 22:00 - 2020年9月4日 04:00

    北京时间2020年9月7日 22:00 - 2020年9月8日 04:00

    北京时间2020年9月9日 22:00 - 2020年9月10日 04:00

    北京时间2020年9月14日 22:00 - 2020年9月15日 04:00

    北京时间2020年9月16日 22:00 - 2020年9月17日 04:00

    北京时间2020年9月21日 22:00 - 2020年9月22日 04:00

    北京时间2020年9月23日 22:00 - 2020年9月24日 04:00

    北京时间2020年9月25日 22:00 - 2020年9月29日 04:00

    升级内容:所有地域的MQ服务(包含TCP、MQTT、HTTP接入方式)。

    升级影响:升级期间MQ控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端会自动重试机制,一般不会影响业务,但会有异常日志。

    升级期间,消息可能会有重复,应用应该按最值实践,做好消息的幂等;同时可能会有消息延迟的现象。如需在控制台进行管理操作,请避开维护时间段。HTTP接入可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。同时,您可使用监控管理功能对重要业务进行监控,具体设置方法请点击MQ控制台左侧监控报警菜单。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【升级】9月3日消息队列AMQP华东1(杭州)地域升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列AMQP】【升级通知】

    升级窗口:北京时间2020年9月3日15:00 - 18:00
    升级内容:华东1(杭州)地域的服务升级。
    升级影响:升级期间消息队列AMQP相关服务访问可能会出现多次闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过 5 分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    滴滴基于 Flink 的实时数仓建设实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 随着滴滴业务的高速发展,业务对于数据时效性的需求越来越高,而伴随着实时技术的不断发展和成熟,滴滴也对实时建设做了大量的尝试和实践。本文主要以顺风车这个业务为引子,从引擎侧、平台侧和业务侧各个不同方面,来阐述滴滴所做的工作,分享在建设过程中的经验。

    1.实时数仓建设目的

    随着互联网的发展进入下半场,数据的时效性对企业的精细化运营越来越重要,商场如战场,在每天产生的海量数据中,如何能实时有效的挖掘出有价值的信息, 对企业的决策运营策略调整有很大帮助。

    其次从智能商业的角度来讲,数据的结果代表了用户的反馈,获取结果的及时性就显得尤为重要,快速的获取数据反馈能够帮助公司更快的做出决策,更好的进行产品迭代,实时数仓在这一过程中起到了不可替代的作用。

    1.1 解决传统数仓的问题

    从目前数仓建设的现状来看,实时数仓是一个容易让人产生混淆的概念,根据传统经验分析,数仓有一个重要的功能,即能够记录历史。通常,数仓都是希望从业务上线的第一天开始有数据,然后一直记录到现在。但实时流处理技术,又是强调当前处理状态的一个技术,结合当前一线大厂的建设经验和滴滴在该领域的建设现状,我们尝试把公司内实时数仓建设的目的定位为,以数仓建设理论和实时技术,解决由于当前离线数仓数据时效性低解决不了的问题。

    现阶段我们要建设实时数仓的主要原因是:

    • 公司业务对于数据的实时性越来越迫切,需要有实时数据来辅助完成决策
    • 实时数据建设没有规范,数据可用性较差,无法形成数仓体系,资源大量浪费
    • 数据平台工具对整体实时开发的支持也日渐趋于成熟,开发成本降低

    1.2 实时数仓的应用场景

    • 实时 OLAP 分析:OLAP 分析本身就是数仓领域重点解决的问题,基于公司大数据架构团队提供的基于 Flink 计算引擎的 stream sql 工具,Kafka 和 ddmq (滴滴自研)等消息中间件,druid 和 ClickHouse 等 OLAP 数据库,提升数仓的时效性能力,使其具有较优的实时数据分析能力。
    • 实时数据看板:这类场景是目前公司实时侧主要需求场景,例如“全民拼车日”订单和券花销实时大屏曲线展示,顺风车新开城当日分钟级订单侧核心指标数据展示,增长类项目资源投入和收益实时效果展示等。
    • 实时业务监控:滴滴出行大量核心业务指标需要具备实时监控能力,比如安全指标监控,财务指标监控,投诉进线指标监控等。
    • 实时数据接口服务:由于各业务线之间存在很多业务壁垒,导致数仓开发很难熟悉公司内全部业务线,需要与各业务线相关部门在数据加工和数据获取方面进行协作,数仓通过提供实时数据接口服务的方式,向业务方提供数据支持。

    640 1.png

    2. 滴滴顺风车实时数仓建设举例

    在公司内部,我们数据团队有幸与顺风车业务线深入合作,在满足业务方实时数据需求的同时,不断完善实时数仓内容,通过多次迭代,基本满足了顺风车业务方在实时侧的各类业务需求,初步建立起顺风车实时数仓,完成了整体数据分层,包含明细数据和汇总数据,统一了 DWD 层,降低了大数据资源消耗,提高了数据复用性,可对外输出丰富的数据服务。

    数仓具体架构如下图所示:

    640 2.png

    从数据架构图来看,顺风车实时数仓和对应的离线数仓有很多类似的地方。例如分层结构;比如 ODS 层,明细层,汇总层,乃至应用层,他们命名的模式可能都是一样的。但仔细比较不难发现,两者有很多区别:

    • 与离线数仓相比,实时数仓的层次更少一些
    • 从目前建设离线数仓的经验来看,数仓的数据明细层内容会非常丰富,处理明细数据外一般还会包含轻度汇总层的概念,另外离线数仓中应用层数据在数仓内部,但实时数仓中,app 应用层数据已经落入应用系统的存储介质中,可以把该层与数仓的表分离。
    • 应用层少建设的好处:实时处理数据的时候,每建一个层次,数据必然会产生一定的延迟。
    • 汇总层少建的好处:在汇总统计的时候,往往为了容忍一部分数据的延迟,可能会人为的制造一些延迟来保证数据的准确。举例,在统计跨天相关的订单事件中的数据时,可能会等到 00:00:05 或者 00:00:10 再统计,确保 00:00 前的数据已经全部接受到位了,再进行统计。所以,汇总层的层次太多的话,就会更大的加重人为造成的数据延迟。
    • 与离线数仓相比,实时数仓的数据源存储不同
    • 在建设离线数仓的时候,目前滴滴内部整个离线数仓都是建立在 Hive 表之上。但是,在建设实时数仓的时候,同一份表,会使用不同的方式进行存储。比如常见的情况下,明细数据或者汇总数据都会存在 Kafka 里面,但是像城市、渠道等维度信息需要借助 Hbase,MySQL 或者其他 KV 存储等数据库来进行存储。

    接下来,根据顺风车实时数仓架构图,对每一层建设做具体展开:

    2.1 ODS 贴源层建设

    根据顺风车具体场景,目前顺风车数据源主要包括订单相关的 binlog 日志,冒泡和安全相关的 public 日志,流量相关的埋点日志等。这些数据部分已采集写入 Kafka 或 ddmq 等数据通道中,部分数据需要借助内部自研同步工具完成采集,最终基于顺风车数仓ods层建设规范分主题统一写入 Kafka 存储介质中。

    命名规范:ODS 层实时数据源主要包括两种。

    • 一种是在离线采集时已经自动生产的 DDMQ 或者是 Kafka topic,这类型的数据命名方式为采集系统自动生成规范为:cn-binlog-数据库名-数据库名 eg:cn-binlog-ihap_fangyuan-ihap_fangyuan
    • 一种是需要自己进行采集同步到 kafka topic 中,生产的topic命名规范同离线类似:ODS 层采用:realtime_ods_binlog_{源系统库/表名}/ods_log_{日志名} eg: realtime_ods_binlog_ihap_fangyuan

    2.2 DWD 明细层建设

    根据顺风车业务过程作为建模驱动,基于每个具体的业务过程特点,构建最细粒度的明细层事实表;结合顺风车分析师在离线侧的数据使用特点,将明细事实表的某些重要维度属性字段做适当冗余,完成宽表化处理,之后基于当前顺风车业务方对实时数据的需求重点,重点建设交易、财务、体验、安全、流量等几大模块;该层的数据来源于 ODS 层,通过大数据架构提供的 Stream SQL 完成 ETL 工作,对于 binlog 日志的处理主要进行简单的数据清洗、处理数据漂移和数据乱序,以及可能对多个 ODS 表进行 Stream Join,对于流量日志主要是做通用的 ETL 处理和针对顺风车场景的数据过滤,完成非结构化数据的结构化处理和数据的分流;该层的数据除了存储在消息队列 Kafka 中,通常也会把数据实时写入 Druid 数据库中,供查询明细数据和作为简单汇总数据的加工数据源。

    命名规范:DWD 层的表命名使用英文小写字母,单词之间用下划线分开,总长度不能超过 40 个字符,并且应遵循下述规则:realtime_dwd_{业务/pub}_{数据域缩写}_[{业务过程缩写}]_[{自定义表命名标签缩写}]

    • {业务/pub}:参考业务命名
    • {数据域缩写}:参考数据域划分部分
    • {自定义表命名标签缩写}:实体名称可以根据数据仓库转换整合后做一定的业务抽象的名称,该名称应该准确表述实体所代表的业务含义
      样例:realtime_dwd_trip_trd_order_base

    2.3 DIM 层

    • 公共维度层,基于维度建模理念思想,建立整个业务过程的一致性维度,降低数据计算口径和算法不统一风险;
    • DIM 层数据来源于两部分:一部分是 Flink 程序实时处理ODS层数据得到,另外一部分是通过离线任务出仓得到;
    • DIM 层维度数据主要使用 MySQL、Hbase、fusion(滴滴自研KV存储) 三种存储引擎,对于维表数据比较少的情况可以使用 MySQL,对于单条数据大小比较小,查询 QPS 比较高的情况,可以使用 fusion 存储,降低机器内存资源占用,对于数据量比较大,对维表数据变化不是特别敏感的场景,可以使用HBase 存储。

    命名规范:DIM 层的表命名使用英文小写字母,单词之间用下划线分开,总长度不能超过 30 个字符,并且应遵循下述规则:dim_{业务/pub}_{维度定义}[_{自定义命名标签}]:

    • {业务/pub}:参考业务命名
    • {维度定义}:参考维度命名
    • {自定义表命名标签缩写}:实体名称可以根据数据仓库转换整合后做一定的业务抽象的名称,该名称应该准确表述实体所代表的业务含义
      样例:dim_trip_dri_base

    2.4 DWM 汇总层建设

    在建设顺风车实时数仓的汇总层的时候,跟顺风车离线数仓有很多一样的地方,但其具体技术实现会存在很大不同。

    第一:对于一些共性指标的加工,比如 pv,uv,订单业务过程指标等,我们会在汇总层进行统一的运算,确保关于指标的口径是统一在一个固定的模型中完成。对于一些个性指标,从指标复用性的角度出发,确定唯一的时间字段,同时该字段尽可能与其他指标在时间维度上完成拉齐,例如行中异常订单数需要与交易域指标在事件时间上做到拉齐。

    第二:在顺风车汇总层建设中,需要进行多维的主题汇总,因为实时数仓本身是面向主题的,可能每个主题会关心的维度都不一样,所以需要在不同的主题下,按照这个主题关心的维度对数据进行汇总,最后来算业务方需要的汇总指标。在具体操作中,对于 pv 类指标使用 Stream SQL 实现 1 分钟汇总指标作为最小汇总单位指标,在此基础上进行时间维度上的指标累加;对于 uv 类指标直接使用 druid 数据库作为指标汇总容器,根据业务方对汇总指标的及时性和准确性的要求,实现相应的精确去重和非精确去重。

    第三:汇总层建设过程中,还会涉及到衍生维度的加工。在顺风车券相关的汇总指标加工中我们使用 Hbase 的版本机制来构建一个衍生维度的拉链表,通过事件流和 Hbase 维表关联的方式得到实时数据当时的准确维度

    命名规范:DWM 层的表命名使用英文小写字母,单词之间用下划线分开,总长度不能超过 40 个字符,并且应遵循下述规则:realtime_dwm_{业务/pub}_{数据域缩写}_{数据主粒度缩写}_[{自定义表命名标签缩写}]_{统计时间周期范围缩写}:

    • {业务/pub}:参考业务命名
    • {数据域缩写}:参考数据域划分部分
    • {数据主粒度缩写}:指数据主要粒度或数据域的缩写,也是联合主键中的主要维度
    • {自定义表命名标签缩写}:实体名称可以根据数据仓库转换整合后做一定的业务抽象的名称,该名称应该准确表述实体所代表的业务含义
    • {统计时间周期范围缩写}:1d:天增量;td:天累计(全量);1h:小时增量;th:小时累计(全量);1min:分钟增量;tmin:分钟累计(全量)
      样例:realtime_dwm_trip_trd_pas_bus_accum_1min

    2.5 APP 应用层

    该层主要的工作是把实时汇总数据写入应用系统的数据库中,包括用于大屏显示和实时 OLAP 的 Druid 数据库(该数据库除了写入应用数据,也可以写入明细数据完成汇总指标的计算)中,用于实时数据接口服务的 Hbase 数据库,用于实时数据产品的 MySQL 或者 Redis 数据库中。

    命名规范:基于实时数仓的特殊性不做硬性要求。

    3. 顺风车实时数仓建设成果

    截止目前,一共为顺风车业务线建立了增长、交易、体验、安全、财务五大模块,涉及 40+ 的实时看板,涵盖顺风车全部核心业务过程,实时和离线数据误差<0.5%,是顺风车业务线数据分析方面的有利补充,为顺风车当天发券动态策略调整,司乘安全相关监控,实时订单趋势分析等提供了实时数据支持,提高了决策的时效性。

    同时建立在数仓模型之上的实时指标能根据用户需求及时完成口径变更和实时离线数据一致性校验,大大提高了实时指标的开发效率和实时数据的准确性,也为公司内部大范围建设实时数仓提供了有力的理论和实践支持。

    4. 实时数仓建设对数据平台的强依赖

    目前公司内部的实时数仓建设,需要依托数据平台的能力才能真正完成落地,包括 StreamSQL 能力,数据梦工程 StreamSQL IDE 环境和任务运维组件,实时数据源元数据化功能等。

    640 3.png

    4.1 基于StreamSQL的实时数据需求开发

    StreamSQL 是滴滴大数据引擎部在 Flink SQL 基础上完善后形成的一个产品。

    使用 StreamSQL 具有多个优势:

    • 描述性语言:业务方不需要关心底层实现,只需要将业务逻辑描述出来即可。
    • 接口稳定:Flink 版本迭代过程中只要 SQL 语法不发生变化就非常稳定。
    • 问题易排查:逻辑性较强,用户能看懂语法即可调查出错位置。
    • 批流一体化:批处理主要是 HiveSQL 和 Spark SQL,如果 Flink 任务也使用 SQL 的话,批处理任务和流处理任务在语法等方面可以进行共享,最终实现一体化的效果。

    StreamSQL 相对于 Flink SQL (1.9 之前版本)的完善:

    • 完善 DDL:包括上游的消息队列、下游的消息队列和各种存储如 Druid、HBase 都进行了打通,用户方只需要构建一个 source 就可以将上游或者下游描述出来。
    • 内置消息格式解析:消费数据后需要将数据进行提取,但数据格式往往非常复杂,如数据库日志 binlog,每个用户单独实现,难度较大。StreamSQL 将提取库名、表名、提取列等函数内置,用户只需创建 binlog 类型 source,并内置了去重能力。对于 business log 业务日志 StreamSQL 内置了提取日志头,提取业务字段并组装成 Map 的功能。对于 json 数据,用户无需自定义 UDF,只需通过 jsonPath 指定所需字段。
    • 扩展UDX:丰富内置 UDX,如对 JSON、MAP 进行了扩展,这些在滴滴业务使用场景中较多。支持自定义 UDX,用户自定义 UDF 并使用 jar 包即可。兼容 Hive UDX,例如用户原来是一个 Hive SQL 任务,则转换成实时任务不需要较多改动,有助于批流一体化。

    Join 能力扩展:

    • 基于 TTL 的双流 join:在滴滴的流计算业务中有的 join 操作数据对应的跨度比较长,例如顺风车业务发单到接单的时间跨度可能达到一个星期左右,如果这些数据的 join 基于内存操作并不可行,通常将 join 数据放在状态中,窗口通过 TTL 实现,过期自动清理。
    • 维表 join 能力:维表支持 HBase、KVStore、Mysql 等,同时支持 inner、left、right、full join 等多种方式。

    4.2 基于数据梦工厂的 StreamSQL IDE 和任务运维

    StreamSQL IDE:

    • 提供常用的SQL模板:在开发流式 SQL 时不需要从零开始,只需要选择一个 SQL 模板,并在这个模板之上进行修修改改即可达到期望的结果
    • 提供 UDF 的库:相当于一个库如果不知道具有什么含义以及如何使用,用户只需要在 IDE 上搜索到这个库,就能够找到使用说明以及使用案例,提供语法检测与智能提示
    • 提供代码在线DEBUG能力:可以上传本地测试数据或者采样少量 Kafka 等 source 数据 debug,此功能对流计算任务非常重要。提供版本管理功能,可以在业务版本不断升级过程中,提供任务回退功能。

    任务运维:任务运维主要分为四个方面

    • 日志检索:Flink UI 上查询日志体验非常糟糕,滴滴将 Flink 任务日志进行了采集,存储在 ES 中,通过 WEB 化的界面进行检索,方便调查。
    • 指标监控:Flink 指标较多,通过 Flink UI 查看体验糟糕,因此滴滴构建了一个外部的报表平台,可以对指标进行监控。
    • 报警:报警需要做一个平衡,如重启报警有多类如 ( 机器宕机报警、代码错误报警 ),通过设置一天内单个任务报警次数阈值进行平衡,同时也包括存活报警 ( 如 kill、start )、延迟报警、重启报警和 Checkpoint 频繁失败报警 ( 如 checkpoint 周期配置不合理 ) 等。
    • 血缘追踪:实时计算任务链路较长,从采集到消息通道,流计算,再到下游的存储经常包括 4-5个环节,如果无法实现追踪,容易产生灾难性的问题。例如发现某流式任务流量暴涨后,需要先查看其消费的 topic 是否增加,topic 上游采集是否增加,采集的数据库 DB 是否产生不恰当地批量操作或者某个业务在不断增加日志。这类问题需要从下游到上游、从上游到下游多方向的血缘追踪,方便调查原因。

    4.3 基于数据梦工厂的实时数据源元数据化(meta化表)

    将 topic 引入成实时表,metastore 统一管理元数据,实时开发中统一管理 DDL 过程。对实时数仓来说,通过元数据化,可以沉淀实时数仓的建设成果,使数仓建模能更好的落地。

    640 4.png

    目前数据梦工厂支持的元数据化实时数据源包括 Postgre、DDMQ、MySQL、Druid、ClickHouse、Kylin、Kafka。

    5. 面临的挑战和解决方案思考

    虽然目前滴滴在实时数仓建设方面已初具规模,但其面临的问题也不容忽视。

    5.1 实时数仓研发规范

    问题:为了快速响应业务需求,同时满足数仓的需求开发流程,迫切需要建设一套面向实时数据开发的规范白皮书,该白皮书需要涉及需求对接、口径梳理、数据开发、任务发布、任务监控、任务保障。

    目前解决方案:目前由数据 BP 牵头,制定了一套面向实时数据指标的开发规范:

    640 5.png

    常规流程:需求方提出需求,分析师对接需求,提供计算口径,编写需求文档。之后由数仓 BP 和离线数仓同学 check 计算口径,并向实时数仓团队提供离线 Hive 表,实时数仓同学基于离线 Hive 表完成数据探查,基于实时数仓模型完成实时数据需求开发,通过离线口径完成数据自查,最终交付给分析师完成二次校验后指标上线。

    口径变更--业务方发起:业务方发起口径变更,判断是否涉及到实时指标,数仓 BP 对离线和实时口径进行拉齐,向离线数仓团队和实时数仓团队提供更口口径和数据源表,实时数仓团队先上测试看板,验收通过后切换到正式看板

    存在的不足:

    • 当针对某个业务进行新的实时数据建设时,会有一个比较艰难的初始化过程,这个初始化过程中,会和离线有较多耦合,需要确定指标口径,数据源,并进行大量开发测试工作
    • 在指标口径发生变更的时候,需要有一个较好的通知机制,目前还是从人的角度来进行判断。

    5.2 离线和实时数据一致性保证

    目前解决办法:由业务、BP、离线数仓共同保证数据源、计算口径与离线一致,数据加工过程,逐层与离线进行数据比对,并对指标结果进行详细测试,数据校验通过并上线后,根据离线周期进行实时和离线数据的校验。

    640 6.png

    待解决的问题:结合指标管理工具,保证指标口径上的一致性,扩展数据梦工厂功能,在指标加工过程中,增加实时离线比对功能,降低数据比对成本。

    6. 未来展望:批流一体化

    虽然 Flink 具备批流一体化能力,但滴滴目前并没有完全批流一体化,希望先从产品层面实现批流一体化。通过 Meta 化建设,实现整个滴滴只有一个 MetaStore,无论是 Hive、Kafka topic、还是下游的 HBase、ES 都定义到 MetaStore 中,所有的计算引擎包括 Hive、Spark、Presto、Flink 都查询同一个 MetaStore,实现整个 SQL 开发完全一致的效果。根据 SQL 消费的 Source 是表还是流,来区分批处理任务和流处理任务,从产品层面上实现批流一体化效果。

    640 7.png

    更多 Flink 技术交流,可以钉钉扫码加入 Flink 的社区大群~

    最新钉群二维码.jpeg

    ]]>
    蚂蚁架构师郭援非:分布式数据库是金融机构数字化转型的最佳路径-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 ​2020年8月26-28日,在中科软科技举办的中国财险科技应用高峰论坛上,蚂蚁集团高级解决方案架构师郭援非发表了《OceanBase分布式关系数据库助力保险业务创新》的主题演讲,为大家分享了蚂蚁集团企业级分布式关系数据库OceanBase的发展历程、产品价值,以及在保险行业的最佳实践应用。

    以下为演讲整理:

    26CA4DDD-F2FF-48d4-805B-95795AA4E1EE.png

    各位朋友,下午好!

    支付宝在全球有十几亿用户,他们可以同时进行可靠、稳定、高效的支付交易,这背后离不开一个重要的秘密武器——OceanBase。今天我很荣幸向大家介绍OceanBase的发展过程、产品特点,以及在保险行业的最佳实践。

    分布式数据库OceanBase的迭代

    OceanBase数据库立项于2010年,第一个客户是淘宝收藏夹。2013年,OceanBase开始了对SQL的支持。2014年,OceanBase开始支持网商银行的所有业务,成为了全球首个应用在金融核心业务系统的分布式关系数据库。从2014年到2016年,支付宝全部的交易系统和核心账务系统都跑在OceanBase之上。多年来全世界十几亿用户在支付宝上发生的所有交易都是由OceanBase来承载,始终稳若磐石。

    1.png

    2017年,我们开始向更多的外部客户输出我们的技术,我们帮助南京银行打造了“鑫云+”互联网金融业务核心。OceanBase服务过的这些场景里既包括新兴的互联网金融核心业务、传统的金融核心业务,也包括对传统数据库的替换、对MySQL的替换,都非常成功。

    2019年, OceanBase参加了世界数据库范围内最权威的TPC-C认证测试,我们取得了6088万tpmC的成绩,是第二名传统数据库公司甲骨文的两倍。TPC-C绝对不是一个简单的跑分测试,实际上,它是要求被测试的对象必须满足数据库的ACID四个事务特征才能够进行的测试。

    2020年,我们又一次参加了TPC-C测试, 达到了7.07亿tpmC,跑出了接近去年12倍的成绩,再次问鼎TPC-C榜首。今年6月份OceanBase正式独立成公司,我们立志于成为世界领先的企业级数据技术解决方案提供商。

    数据库形态演进

    金融业的核心业务系统,包括保险行业的核心业务系统仍旧大规模使用传统集中式数据库。这种方案带来了两个问题,一是集中式数据库造价非常高,来自于两方面,一方面是软件License很贵;另一方面是硬件很贵,因为它必须使用高端硬件,比如IBM的大机、EMC的高端存储,这些都是非常昂贵的,时间长了再大的企业也很难承受;二是扩展性非常不好,传统集中式数据库都是基于共享存储(shared-storage)的方式做横向扩展,当面临关键业务系统发展需要不断扩容的时候,这种方案有心无力,难以做有效的支撑。

    4.png

    对于上面这种方案业界提了一种补丁方案,十几年前曾有人提出既然传统集中式数据库很贵,那么在单机开源的基础上加一个语法兼容,这样就解决了它昂贵的问题。但是发展到今天,我们在金融领域的核心系统还没有看到广泛使用这种方案。原因很简单,金融业核心系统使用传统集中式数据库主要不是因为它们在语法上的完善和优秀,根本原因在于它们的稳定可靠,而这个补丁方案做不到这些,同时这个方案也没有解决扩展性的问题。

    说到扩展性同样是在十几年以前,最早在互联网企业中提出了这么一种方案,叫做分库分表,一个典型的例子就是在多台单机的MySQL上,用Proxy把MySQL单机集成为MySQL集群,提供很好的算力。这种方案同样具有很大的问题,当计算发生在单个MySQL机器内部的时候一切很好,但是一旦用户的负载跨越MySQL单机边界的时候,你可能会发现分库分表系统的表现并不尽如人意。具体来说,在全局事务、数据强一致性、负载均衡以及复杂SQL方面都不能很好的支持,所以这种方案的使用场景非常受限。截止到今天,在银行、保险、券商行业的核心业务系统中依然没有广泛使用过这种方案。

    那么到底是否存在这样一种数据库系统,它既可以提供传统集中式数据库的高可靠性,又成本可控,同时扩展性良好?在这个基础之上还能够满足事务的ACID、具备强一致性、复杂SQL的处理能力?答案是有,而且可以更好,那就是OceanBase。

    OceanBase的三大独特价值

    首先,OceanBase是一款完全自主研发的分布式关系数据库,代码是百分之百自研的,包括SQL引擎、存储引擎都是我们的工程师自己一行一行代码写出来的。这一点很重要,它意味着当你的数据库出现问题的时候,我们的工程师不需要求助开源社区,我们自己就可以兜底,这对于要求稳定,要求可靠,要求有可预期性的金融领域的核心业务系统是一个刚需,这一点OceanBase可以充分提供。

    2.png

    第二, OceanBase是全世界唯一一款经过长时间复杂金融场景验证的原生分布式数据库。数据库其实是一个非常复杂的系统,它的复杂度甚至不亚于操作系统,对它的各种复杂应用场景的测试不是一个简单的测试程序可以穷尽的。目前,可能唯一有效的方法就是在实践中找到足够复杂、足够多的场景对数据库进行反复的使用,使用到最后没有问题为止大家才会信任它。我们非常高兴的告诉大家,OceanBase做到了。

    第三,OceanBase数据库是全世界范围内唯一一款基于普通的X86云服务器,在TPC-C领域获得第一名的原生分布式数据库。登顶TPC-C榜单意味着OceanBase不但可以提供世界级的性能,而且可以提供单机数据库一样的完整体验,这一点意味着用户可以像使用单机数据库一样使用OceanBase。

    OceanBase核心特性

    在OceanBase数据库中,我们通过“五高一多”的特点定义了分布式数据库的领先标准。

    3.png

    高可用

    我们通过Paxos协议和多副本技术,可以提供很好的高可用性,当出现灾难的时候我们可以做到RPO=0,RTO<30秒,这完全可以满足企业的容灾需求。同时由于我们使用的是Paxos协议, Paxos协议是支持日志空洞的,这就带来一个好处,当在网络出现波动的时候,我们可以给到更好的性能保证。

    OceanBase的高可用性,是完全在通用X86服务器和普通硬件上提供出来的,没有使用任何高端硬件,所以我们的成本会更低一些。

    高扩展

    在实际生产系统中单个OceanBase集群内的服务器可以达到100台以上。而且OceanBase具有很好的线性扩展比。高扩展性还体现在扩容时的自动负载均衡,这意味着当DBA需要扩容的时候,所做的只是增加一台机器到集群里,接下来,我们的OceanBase管理系统会自动的根据系统的负载情况,把数据迁移到新加的机器上。整个扩容缩容过程中,业务没有感知。

    高性能

    我们采用了很多先进的技术来提高数据库的性能。比如LSM Tree、无锁结构、消除磁盘的随机写等等,这些技术帮助我们充分使用硬件的能力,再辅以高扩展性,我们就可以提供一个世界级性能的OceanBase集群。

    在实际的生产系统里,我们可以在峰值的时候提供6100万次每秒,单表最大容量可以到3200亿行。和高性能伴随的是低成本,因为我们采用了LSM Tree结构,所以当数据落盘的时候是更有组织的,可以做到1:3的压缩比。

    高透明

    我们实现了不少关键技术,比如全局一致性快照、全局索引、自动事务两阶段提交。使用OceanBase数据库,应用就像使用一台单机数据库一样,不需要做针对分布式数据库的特别感知和修改。

    高兼容

    我们在一套OceanBase集群上同时为您提供两套生态,一套是Oracle生态,一套是MySQL生态,有效地降低业务迁移改造的成本。同时我们和国内主流的操作系统、芯片也都做了互认的支持,可以有效满足技术供应链安全的需求。

    多租户

    多租户就是在一个OceanBase集群上,可以建立很多个租户,每个租户对应一个Oracle实例或者是MySQL实例。可以用这个特性实现业务大集中,把以前全国一省一套的传统集中式数据库都集中在一个OceanBase数据库集群那里。也可以做不同种类的小业务的集中,之前几十个小的业务现在都集中在一个OceanBase集群里统一管理,这个特性不但可以有效利用集群资源,更可以有效降低 DBA的运维成本。

    OceanBase产品定位与形态

    OceanBase可以提供拥有世界级性能的OLTP引擎,同时也提供一个非常优秀的OLAP引擎,这意味着辅以刚才提到的多租户功能,您可以在一个OceanBase的集群里创建不同的实例,有的专门用于交易,有的专门用于分析,当然也可以创建一个大的实例,既做交易又做分析。OceanBase目前真正做到了一套HTAP引擎,它能够同时支持交易和分析型业务。

    5.png

    OceanBase的容灾架构,既支持经典的主备容灾方式,同时也支持基于Paxos协议的容灾方式,我们支持同机房三副本,也支持同城三机房,甚至是两地三中心,三地五中心,这些不同的容灾级别可以完全满足您的容灾诉求。

    关于部署模式,如果您希望和阿里云其他的优秀产品,比如和金融级分布式中间件SOFA或者对象云存储OSS一起使用,那么OceanBase可以在阿里云的专有云上为您输出。如果您只想使用OceanBase,我们也可以以一个纯License的方式提供给您,就在您的服务器上安装我们的软件,一样可以获得一个高可用、高扩展、高性能的集群。如果您希望只是购买一个公有云的服务,您可以在阿里云的公有云上购买一个OceanBase的RDS。这些不同选项可以无缝适配您各种不同的需求。

    OceanBase在保险业的实践

    OceanBase在保险业的最佳实践,一个很好的案例就是人保健康险。如果不熟悉的朋友可以打开您的手机支付宝,在上面搜“好医保”就可以看到人保健康险的产品。人保健康利用了金融级分布式数据库OceanBase和金融级分布式中间件SOFAStack解决了高并发保单处理能力速度慢的问题,目前每日平均出单量在15万左右。

    自2018年4月11日人保健康险好医保产品成功上线后,截止到目前,好医保已成为支付宝网红保险产品。OceanBase上线中国人保健康以后,取得了显著的收益:

    处理能力提升了上千倍,并可根据业务情况动态扩容

    2018款好医保住院医疗保险,每天1万单的日结文件,处理速度从以前的4小时缩短至6分钟

    新产品上线时间缩短80%以上,从原来的数周缩短至数天

    采用多种协议转换和标准化对接流程,使外部渠道接入效率提升6倍,支持第三方业务快速拓展

    谢谢大家听完我的汇报,期待着未来我们可以用OceanBase数据库更好地服务于保险行业,和各位好朋友一起打造面向未来的保险业的核心业务系统。

    ]]>
    阿里云操作系统选择说明指南 如何选择阿里云服务器操作系统?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    如何轻松搞定SAP HANA数据库备份?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    企业上云等级保护三级解决方案-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文介绍企业上云满足等保合规三级的最佳实践。

    1.场景描述

    网络安全法中明确要求国家实行网络安全保护制度,网络运营者有义务履行等级保护制度要求。阿里云除了提供满足等保合规要求的云平台外,还为用户的应用系统提供完整的云原生、高性价比的等保三级解决方案。

    2.解决的问题

    • 等保2.0合规要求
    • 云上高等级安全体系建设

    3.部署架构图

    图1:企业上云等保三级合规部署架构图.png

    图1:企业上云等保三级合规部署架构图

    4.选用的产品

    更多有关以下产品的介绍,可点击这里或扫描文后二维码查看相关产品详情。

    4.1云安全中心

    云安全中心是一个实时识别、分析、预警安全威胁的统一安全管理系统,通过防勒索、防病毒、防篡改、合规检查等安全能力,帮助用户实现威胁检测、响应、溯源的自动化安全运营闭环,保护云上资产和本地主机并满足监管合规要求。

    4.2 Web应用防火墙

    阿里云Web应用防火墙(WAF)对网站或APP的业务流量进行恶意特征识别及防护,将正常、安全的流量回源到服务器。避免网站服务器被恶意入侵,保障业务的核心数据安全,解决因恶意攻击导致的服务器性能异常问题。

    4.3云防火墙

    集中管理公网IP的访问策略,内置威胁入侵防御模块(IPS),支持失陷主机检测、主动外联行为的阻断、业务间访问关系可视,留存6个月网络流量日志,等保必备。

    4.4 SSL证书

    在云上签发各品牌数字证书,实现网站HTTPS化,使网站可信,防劫持、防篡改、防监听、安全加密。统一生命周期管理,简化证书部署,一键分发到CDN、负载均衡、OSS等其它云上产品。

    4.5堡垒机

    集中管理资产权限,全程记录操作数据,实时还原运维场景,助力企业用户构建云上统一、安全、高效运维通道;保障云端运维工作权限可管控、操作可审计、合规可遵从。

    4.6数据库审计

    智能解析数据库通信流量,细粒度审计数据库访问行为,帮助企业精准识别、记录云上数据安全威胁,为云端数据库提供全方位的安全、诊断、维护及合规能力。

    4.7日志服务SLS

    行业领先的日志大数据解决方案,一站式提供数据收集、清洗、分析、可视化和告警功能。全面提升海量日志处理能力,实时挖掘数据价值,智能助力研发/运维/运营/安全等场景。

    4.8密钥管理服务

    密钥管理服务(KMS)提供安全合规的密钥托管和密码服务,助您轻松使用密钥来加密保护敏感的数据资产,控制云上的分布式计算和存储环境。您可以追踪密钥的使用情况,配置密钥的自动轮转策略,以及利用托管密码机所具备的中国国家密码管理局或者FIPS认证资质,来满足您的监管合规需求。

    5.详细信息

    点击这里或扫描下方二维码查看最佳实践详情。

    6.更多最佳实践

    点击这里或扫描下方二维码查看更多阿里云最佳实践。

    下载.png

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。

    image.png

    ]]>
    企业上云等保二级合规-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文档介绍企业上云满足等保合规2.0的最佳实践。

    1.场景描述

    阿里云安全帮助您快速、省心地通过等保合规。 在阿里云,您可享受一站式等保测评,包括完备的攻击防护、数据审计、数据备份与加密、安全管理服务。可充分利用云平台的免费管理软件,包括RAM、ActionTrail、云监控等,满足等保2.0需求。

    2.解决的问题

    • 等保2.0合规要求
    • 云上安全体系建设

    3.部署架构图

    图1.png

    图1:企业上云等保合规2.0部署架构图

    4.选用的产品

    更多有关以下产品的介绍,可点击这里或扫描文后二维码查看相关产品详情。

    4.1云安全中心

    云安全中心是一个实时识别、分析、预警安全威胁的统一安全管理系统,通过防勒索、防病毒、防篡改、合规检查等安全能力,帮助用户实现威胁检测、响应、溯源的自动化安全运营闭环,保护云上资产和本地主机并满足监管合规要求。

    4.2Web应用防火墙

    阿里云Web应用防火墙(WAF)对网站或者APP的业务流量进行恶意特征识别及防护,将正常、安全的流量回源到服务器。避免网站服务器被恶意入侵,保障业务的核心数据安全,解决因恶意攻击导致的服务器性能异常问题。

    4.3云防火墙

    集中管理公网IP的访问策略,内置威胁入侵防御模块(IPS),支持失陷主机检测、主动外联行为的阻断、业务间访问关系可视,留存6个月网络流量日志,等保必备。

    4.4SSL证书

    在云上签发各品牌数字证书,实现网站HTTPS化,使网站可信,防劫持、防篡改、防监听、安全加密。统一生命周期管理,简化证书部署,一键分发到CDN、负载均衡、OSS等其它云上产品。

    4.5数据库审计

    智能解析数据库通信流量,细粒度审计数据库访问行为,帮助企业精准识别、记录云上数据安全威胁,为云端数据库提供全方位的安全、诊断、维护及合规能力。

    4.6日志服务 SLS

    行业领先的日志大数据解决方案,一站式提供数据收集、清洗、分析、可视化和告警功能。全面提升海量日志处理能力,实时挖掘数据价值,智能助力研发/运维/运营/安全等场景。

    5.详细信息

    点击这里或扫描下方二维码查看最佳实践详情。

    6.更多最佳实践

    点击这里或扫描下方二维码查看更多阿里云最佳实践。

    image.png

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。

    image.png

    ]]>
    CentOS-7 安装 Redis-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 wget安装
    [root@RedisSrv1 ~]# yum install wget

    安装gcc依赖

    [root@RedisSrv1 ~]# yum install gcc -y
    -- 请先检查gcc的版本是否低于5,如果是请先升级,可以使用以下命令:
    [root@RedisSrv1 redis-stable]# gcc -v
    CentOS7默认安装的是4.8.5,而redis6.0只支持5.3以上版本,这里将gcc升级到9
    [root@RedisSrv1 redis-stable]# yum -y install centos-release-scl
    [root@RedisSrv1 redis-stable]# yum install devtoolset-9-gcc*
    gcc版本切换
    临时切换:scl enable devtoolset-9 bash
    永久切换:echo “source /opt/rh/devtoolset-9/enable” >> /etc/profile

    下载最新稳定版 Redis

    [root@RedisSrv1 /]# cd /opt/
    [root@RedisSrv1 /]# wget http://download.redis.io/releases/redis-stable.tar.gz

    解压redis安装包

    [root@RedisSrv1 opt]# tar -zxvf redis-stable.tar.gz

    进到解压后的redis目录中进行编译

    [root@RedisSrv1 opt]# cd redis-stable
    
    [root@RedisSrv1 redis-stable]# make MALLOC=libc
    make[1]: Leaving directory `/opt/redis-stable/src'
    
    编译完成后,进入到src目录下,可以看到,生成了可执行文件
    生成了src目录文件之后,进入src(源文件目录)继续编译
    [root@RedisSrv1 redis-stable]# cd src/
    
    #安装到 /usr/local/redis-stable 目录
    [root@RedisSrv1 src]# make install PREFIX=/usr/local/redis-stable
    
    Hint: It's a good idea to run 'make test' ;)
    
        INSTALL install
        INSTALL install
        INSTALL install
        INSTALL install
        INSTALL install
    
     --将配置文件移动到/home/redis/目录
    cp /opt/redis-stable/redis.conf /usr/local/redis-stable
    --修改文件权限  
    chmod -R 700 /usr/local/redis-stable/
    [root@RedisSrv1 redis-stable]# cd /usr/local/redis-stable/bin
    [root@RedisSrv1 bin]# ./redis-server /usr/local/redis-stable/redis.conf
    
    
    --关闭redis进程
    [root@server01 src]# ps -ef | grep redis
    root       1177      1  0 09:09 ?        00:00:00 ./redis-server 0.0.0.0:6379
    root       1185   1147  0 09:11 pts/0    00:00:00 grep --color=auto redis
    [root@server01 src]# 
    [root@server01 src]# ps -aux | grep redis
    root       1177  0.0  0.2 144008  2028 ?        Ssl  09:09   0:00 ./redis-server 0.0.0.0:6379
    root       1187  0.0  0.0 112708   976 pts/0    R+   09:11   0:00 grep --color=auto redis
    [root@server01 src]# 
    [root@server01 src]# kill -9 1177
    [root@server01 src]# 
    [root@server01 src]# ps -aux | grep redis
    root       1189  0.0  0.0 112708   980 pts/0    R+   09:12   0:00 grep --color=auto redis
    [root@server01 src]# 
    
    开放防火墙6379端口
    firewall-cmd --zone=public --add-port=6379/tcp --permanent
    
    重新加载防火墙设置
    firewall-cmd --reload
    
    查看是否生效
    firewall-cmd --list-ports
    
    

    卸载Redis

    查看进程
    [root@RedisSrv1 src]# ps aux |grep redis
    
    杀掉进程
    kill -9 进程号
    
    查看相关文件
    [root@RedisSrv1 src]# find / -name "redis*"
    
    删除文件
    rm -rf 文件
    

    Redis 自启动

    [root@RedisSrv1 ~]# vim /usr/lib/systemd/system/redis.service
    [Unit]
    Description=Redis persistent key-value database
    After=network.target
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=forking
    PIDFile=/var/run/redis_6379.pid
    ExecStart=/usr/local/redis-stable/bin/redis-server /usr/local/redis-stable/redis.conf --supervised systemd
    ExecReload=/bin/kill -USR2 $MAINPID
    ExecStop=/usr/local/redis-stable/bin/redis-cli -p 6379 shutdown
    
    [Install]
    WantedBy=multi-user.target
    
    [root@RedisSrv1 ~]# systemctl daemon-reload
    [root@RedisSrv1 ~]# systemctl start redis
    [root@RedisSrv1 ~]# systemctl status redis
    [root@RedisSrv1 ~]# systemctl stop redis
    ]]>
    操作审计日志分析实战一:使用 SQL 分析投递到 OSS 中的操作审计日志-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 简单了解原理

    (1)在操作审计创建跟踪可以将账号下发生的云上管控操作日志持续投递到SLS Logstore和OSS Bucket;
    (2)在数据湖分析(DLA)服务,可以通过简单的设置将操作日志从OSS Bucket导入DLA。

    dla-oss.png

    DLA是一款基于Serverless的强大的交互式数据查询分析服务,能够便捷的对不同格式的数据源进行格式化整合并使用统一SQL查询分析。将OSS Bucket 中的操作日志导入DLA后,
    (1)DLA将OSS Bucket内以Array形式保存的一行多条日志记录拆分为多条数据;
    (2)DLA将以JSON保存的每条操作日志转换为结构化的数据表。
    这使面向OSS Bucket的数据解析被大大的简化,直接实现可视化的标准SQL分析。

    需求背景.png

    开始实践

    Step1:确认最佳实践的前提条件

    1、确认您已经在操作审计创建了跟踪。如果还未创建跟踪,请先完成创建账号追踪操作,并配置将操作记录投递到对象存储(OSS)。
    2、确认开通了DLA服务,请参见开通DLA服务

    Step2:在DLA服务中创建Schema

    1、登录Data Lake Analytics管理控制台
    2、在页面左上角,选择与OSS所在地域一致的DLA地域。
    3、单击左侧导航栏的数据湖构建 > 数据入湖,在数据入湖页面单击ActionTrail日志清洗中的进入向导。
    4、在ActionTrail日志清洗页面,根据页面提示进行参数配置。

    创建Schema_1.png

    5、完成上述参数配置后单击创建,创建Schema。
    服务端预设的操作审计日志Schema结构如下方表格所示。

    1111111111111111111.png

    Schema表结构介绍

    表格长图.png

    Step3:开启同步

    Schema创建成功后,ActionTrail投递到OSS Bucket中的日志数据尚未同步到DLA中,DLA中尚未创建OSS日志文件对应的表,您还需要通过单击立即同步来创建表并同步表数据。
    1、单击立即同步启动数据同步任务。

    在配置页签下,单击更新更新Schema配置。

    立即同步.png

    2、单击表页签,查看数据同步情况。

    元数据管理.png

    数据同步到DLA以后,您就可以在DLA中使用标准SQL语法对ActionTrail日志数据进行分析。

    Step4:数据分析示例

    1、单击DLA控制台左侧 SQL执行 选项卡,选择目标前面设置的数据库。

    SQL界面.png

    2、输入查询语句,在这里输入单击 同步执行

    输入SQL.png

    3、得到查询结果

    查询结果.png

    您可以使用任何符合SQL语法的语句去对DLA中的日志信息进行查询。

    常用查询案例

    案例1:查询某个AK的操作日志

    1、输入语句:select * from action_trail where user_identity_access_key_id = '你的目标AK' limit 20;

    案例查询1语句.png

    2、单击 同步执行 得到前20条符合条件的记录如下

    案例1查询结果.png

    案例2:查询某个AK访问某个产品的操作日志
    1、输入语句,查询AK为指定值,调用Ecs服务的记录:select * from action_trail where user_identity_access_key_id = '你的目标AK' AND service_name = 'Ecs' limit 20;

    案例查询2语句.png

    2、单击 同步执行 得到前20条符合条件的记录如下
    案例2查询结果.png

    ]]>
    阿里云高效基因序列检索助力新冠肺炎病毒序列快速分析-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 AnalyticDB for MySQL是云端托管的PB级高并发低延时数据仓库,通过AnalyticDB for MySQL向量检索功能构建基因检索系统,支持毫秒级针对10亿级别的向量数据进行查询分析,更加快速、高效地为新冠肺炎病毒防控、研发治疗药物以及相关疫苗提供帮助。

    1.背景信息

    2019年年底,中国的新兴商业中心武汉,爆发了一种名为新型冠状病毒肺炎(简称新冠肺炎)的疫情。在新冠肺炎流行的两个多月中,中国造成了3300多人死亡,8万2千多人感染。随着疫情的进一步蔓延,目前已经横跨了109个国家,造成了80多万人感染,4万多人失去了生命。到目前为止,疫情使得50多个国家停摆,全世界范围内造成了数千亿美元的经济损失。在疫情防治期间,阿里云提供了高效基因序列检索技术,助力新冠肺炎病毒序列分析。

    2.基因序列检索技术应用范围和现状

    对于当下疫情,基因序列检索技术主要应用于以下场景:

    • 用于新冠肺炎的溯源和分析,帮助疫情防控找到病毒宿主,做好有效防范。
      通过基因序列检索技术,发现蝙蝠和穿山甲身上的冠状病毒RNA序列匹配度高达96%和99.7%,可以推断蝙蝠和穿山甲很可能是新冠肺炎的宿主。
    • 用于分析新冠肺炎病毒的复制和传播过程,为研发治疗药物和疫苗提供帮助。
      通过基因序列检索技术对基因序列按功能区域划分,了解各个模块的功能,从而更好地分析出病毒的复制和传播过程,进而找到关键节点,为研发治疗药物和疫苗提供帮助。
    • 用于检索到与冠状病毒相似的病毒基因序列。
      基因序列检索技术也可以检索与新冠肺炎病毒相似的病毒基因序列,例如SARS、MERS等病毒,从而借鉴相关药物靶点设计机制,更快、更高效地研发检测试剂盒、疫苗以及相关的治疗药物。

    基于当下疫情的快速蔓延,当前的基因匹配算法太慢,迫切需要高效匹配算法进行基因序列检索。阿里云AnalyticDB for MySQL团队将基因序列片段转化成对应的1024维特征向量,将两个基因片段的匹配问题,转换成了两个向量间的距离计算问题,从而大大降低了计算开销,实现毫秒级返回相关基因片段,完成基因片段的首次筛选。然后,使用基因相似计算BLAST算法,完成基因相似度的精确排查,从而高效率完成基因序列的匹配计算。匹配算法从原来O(M+N)的复杂度降低到O(1)。同时,阿里云AnalyticDB for MySQL提供强大的机器学习分析工具,通过基因转向量技术,将局部的和疾病相关的关键靶点基因片段转成特征向量,用于基因药物的研发,大大加速了基因分析过程。

    3. AnalyticDB for MySQL基因检索系统

    新冠肺炎病毒的RNA序列可以用一串核酸序列(又称碱基序列)表示,RNA序列含有四种核苷酸,分别用A、C、G和T表示,分别代表腺嘌呤、胞嘧啶、鸟嘌呤、胸腺嘧啶。每个字母代表一种碱基,无间隔排列在一起。每一个物种的RNA序列均不相同但又有规律,基因检索系统可以通过输入一串病毒的基因片段,检索相似的基因,用来对病毒的RNA序列进行分析。
    为方便演示AnalyticDB for MySQL基因片段检索方法,我们从GenBank下载了大量病毒的RNA片段,并将GenBank内部关于病毒的论文以及Google Scholar中相关病毒的论文导入AnalyticDB for MySQL基因检索数据库中。
    AnalyticDB for MySQL基因检索的演示界面如下图所示,将新冠肺炎病毒的序列(COVID-19)上传到AnalyticDB for MySQL基因检索系统中,AnalyticDB for MySQL基因检索系统只需几毫秒即可检索到相似的基因片段(当前示例系统只返回匹配度超过0.8的基因片段)。从返回的基因片段得出穿山甲携带的新冠肺炎病毒(GD/P1L)、蝙蝠携带的新冠肺炎病毒(RaTG13)以及SARS和MARS病毒,其中GD/P1L的序列匹配度最高为0.974,由此推断出新冠肺炎病毒很可能是通过穿山甲传染到人的。

    图1:AnalyticDB for MySQL基因检索演示界面.png

    图1:AnalyticDB for MySQL基因检索演示界面

    如果RNA片段非常相似,说明这两个RNA可能有相似的蛋白质表达和结构。通过基因检索工具,可以看到SARS和MARS与新冠肺炎病毒的匹配度为0.8以上,说明可以将一些SARS或者MARS的研究成果应用到新冠肺炎病毒上。系统提取了每种病毒的论文,通过文本分类算法,将论文划分为检测类、疫苗类和药物类,例如下图为SARS的七种检测方法、四种疫苗接种方法和10中治疗药物。其中,对SARS有效的荧光定量PCR检测,目前正应用于新冠肺炎病毒的检测;基因疫苗的方法以及诱导体内免疫疫苗的方法,也正在展开研究;治疗药物中瑞德西韦以及相关的干扰素也都用于新冠肺炎病毒的治疗上。

    图2:SARS病毒相关论文检索分类结果.png


    图2:SARS病毒相关论文检索分类结果

    单击上图干扰素链接可以查看相关论文,当前系统调用了自动翻译软件,抽取中文版文件名关键词作为文件名,方便用户阅读。

    图3:论文查看效果.png


    图3:论文查看效果

    4.实现架构

    图4:基因检索实现架构.png


    图4:基因检索实现架构

    AnalyticDB for MySQL基因检索系统中,AnalyticDB for MySQL负责存储和查询所有结构化数据(例如基因序列的长度,长度包含基因的论文名称、基因种类、DNA或者RNA等)和基因序列产生的特征向量。查询数据时,通过基因向量抽取模型将基因转化成向量,在AnalyticDB for MySQL向量数据库中进行粗排检索,然后在返回的向量匹配结果集中使用经典的BLAST算法进行精确检索,返回最相似的基因序列。
    AnalyticDB for MySQL基因检索系统的核心是基因向量抽取模型,该模块可以将核苷酸序列转化成向量。目前AnalyticDB for MySQL抽取了各种病毒的RNA全部序列样本进行训练,可以非常方便地对病毒的RNA进行相似度计算。同时,基因向量抽取模型也可以扩展应用于其他物种基因检索。

    5.基因向量抽取算法

    目前词向量技术已经非常成熟,被广泛应用于机器翻译、阅读理解、语义分析等相关领域,并取得了巨大成功。词向量化采用了分布式语义的方法表示一个词的含义,一个词的含义就是这个词所处的上下文语境。例如高中英语中的完形填空题,一篇短文空出10个地方,学生根据空缺词的上下文语境选择合适的词。也就是说上下文语境能够准确地表达这个词,如果某位同学选择了正确的选词,表示该同学理解了空缺词的含义。因此,通过上下文词的关系,采用词向量算法,可以为每个词生成一个向量,通过计算两个词向量之间的相似度,得到两个词的相似度。
    同样的道理,基因序列的排列具有一定的规律,并且每一部分基因序列所表达的功能和含义不同。可以将很长的基因序列划分成小的单元片段(也就是词)进行分析,并且这些词也有上下文语境,这些词相互连接、相互作用共同完成相对应的功能,形成合理的表达。因此,生物科学家们采用词向量算法对基因序列单元进行向量化,两个基因单元相似度很高,说明需要这两个基因单元共同来表达和完成相应的功能。
    总体而言,AnalyticDB for MySQL基因向量抽取算法分为三步:
    (1)在氨基酸序列中定义词。生物信息学中用K-mers来分析氨基酸序列,K-mer是指将核酸序列分成包含K个碱基的字符串,即从一段连续的核酸序列中迭代选取长度为K个碱基的序列,若核酸序列长度为L,K-mer长度为K,那么可以得到L-K+1个K-mers。例如下图所示,假设某序列长度为12,设定选取的k-mer长度为8,则得到(12-8+1=5)5个5-mers。这些k-mer,就是氨基酸序列中的词。

    图5:8-mers核酸序列示意图.png


    图5:8-mers核酸序列示意图

    (2)找到氨基酸序列的上下文语境,将基因序列中的词转换成1024维向量。对于词向量算法而言,另一个重要的问题就是上下文的语境。AnalyticDB for MySQL基因向量抽取算法在氨基酸片段中选择一个长度为L的窗口,该窗口内的氨基酸片段可认定为在同一语境内。例如CTGGATGA是一段核酸序列,选取了长度为10的窗口,AnalyticDB for MySQL基因向量抽取算法将CTGGATGA转换成5个5-mers即{AACTG, ACTGG, CTGGA, GGATG, GATGA}。对于其中一个5-mer{CTGGA}而言,另外四个{AACTG, ACTGG, GGATG, GATGA}5-mers就是当前5-mer {CTGGA}的上下文语境。AnalyticDB for MySQL基因向量抽取算法套用词向量空间训练模型,对已有生物基因的K-mers进行训练,便可将一个K-mer(基因序列中的一个词)转换成1024维向量。
    (3)类似于词向量模型,K-mer向量模型也拥有和词向量模型相似的数理计算性质。公式一说明核苷酸序列ACGAT的向量减去GAT序列的向量和AC序列的向量距离非常接近。公式二说明核苷酸序列AC的向量加上ATC序列的向量和ACATC序列的向量距离也很接近。因此,根据这些数理特征,计算一个长氨基酸序列向量时,可以将这个序列中每一段的K-mer序列进行累加,最后进行归一化就能得到整个氨基酸序列的向量。同时,为提升精度,可以将基因片段看作一个文本,使用doc2vec4将整个序列转换成向量进行计算。为进一步验证算法性能,AnalyticDB for MySQL基因向量抽取算法计算了常用于基因检索库中的BLAST算法序列与基因转向量l2距离序列的相似度,两个序列的斯皮尔曼等级相关系数是0.839。以上得出结论,将DNA序列转换成向量用于相似基因片段的初次筛选,是有效且可行的。

    6.向量检索功能概述

    一般包含向量检索的应用系统中,开发者通常会使用向量检索引擎(例如Faiss)存储向量数据,然后使用关系型数据库存储结构化数据。因此,查询时也需要交替查询两个系统,明显额外增加了开发人员的工作量,数据查询性能也不是最优。
    AnalyticDB for MySQL是云端托管的PB级高并发低延时数据仓库,可以毫秒级针对10亿级别的向量数据进行查询,100毫秒级别的响应时间。AnalyticDB for MySQL全面兼容MySQL协议以及SQL:2003语法标准,其向量检索功能支持对图像、文本推荐、声纹、核苷酸序列等相似性进行查询和分析,目前在多个城市的安防项目中已大规模部署了AnalyticDB for MySQL。
    AnalyticDB for MySQL支持结构化和非结构化数据的近似检索和分析,通过SQL接口即可快速搭建基因检索或者基因+结构化数据混合检索等系统。在混合检索场景中AnalyticDB for MySQL的优化器会根据数据的分布和查询条件选择最优执行计划,在保证数据召回率的同时,得到最优的性能。例如,通过以下一条SQL即可检索RNA核酸序列。

    -- 查找RNA和提交的序列向量相近的基因序列。
    select  title, # 文章名
            length, # 基因长度
            type, # mRNA或DNA等
            l2_distance(feature, array[-0.017,-0.032,...]::real[]) as distance # 向量距离 
    from demo.paper a, demo.dna_feature b
    where a.id = b.id
    order by distance; # 用向量相似度排序
    

    上述SQL中表demo.paper用于存储上传的每篇文章的基本信息,demo.dna_feature存储各个物种的基因序列对应的向量。通过基因转向量模型,将要检索的基因转成向量[-0.017,-0.032,...],然后在AnalyticDB for MySQL数据库中进行检索。
    当前系统也支持结构化信息+非结构化信息(核苷酸序列)的混合检索,例如查找和冠状病毒相关的类似基因片段时,只需要在SQL中增加where title like'%COVID-19%'即可。
    本文参考的相关文献,感兴趣的读者可访问这里或扫描下方二维码了解详情。

    二维码.png

    作者:李倩

    阿里云云原生数据仓库AnalyticDB产品经理

    阿里云云原生数据仓库AnalyticDB产品经理,见证了AnalyticDB从初创到至今的壮大发展,参与了所有重大功能设计。

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。

    image.png

    ]]>
    钉钉猛增40倍流量压力 阿里云DBA如何应对-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1.背景

    由于受新型冠状病毒感染的肺炎疫情影响,钉钉流量从春节后开始出现了飞跃性增长。此次疫情流量主要来源于钉钉远程办公和在线教育功能,从字面来看,好像只是钉钉的两个业务功能,但在钉钉内部依赖模块不下20个,主要有消息、视频会议、直播、家校、健康打卡等业务场景。如何保障超过20个业务在如此爆发式增长下的性能和稳定性,是对钉钉后台系统、数据库系统的一个很大挑战。
    本文将从数据库DBA的视角来介绍我们是如何打赢这场“战役”的,在这个过程中我们究竟遇到了哪些挑战,我们是如何组织我们的团队,如何思考,如何真正利用技术克服这些挑战,最后通过这场战役,我们又沉淀了哪些经验及技术。

    2.对数据库系统的挑战

    数据库是钉钉业务系统运行的强依赖,在这种类似双11的场景下,如何规划部署数据库成了稳定性中最重要的一环。但是这次的战役来得突然,并没有很多时间准备,因此面临了非常多的困难与挑战,总结下来有以下3点:
    (1)系统所需要的容量是多少,无法预估
    以消息模块为例,在春节前,钉钉消息日常流量峰值不到千万,第一次容量评估,大家给2月3号定的目标是日常峰值的3倍。随着2月10号开课高峰的到来,又将2月10号的目标调整为10倍。之后又因为2月17号开学季的到来,再次将目标调整为40倍。所以总容量相比日常峰值翻了40倍!
    (2)时间紧,扩容需求众多,资源不足
    疫情流量的猛增,给系统带来的冲击不亚于每年的双11。电商会花半年时间准备双11,但这次留给我们的时间只能以小时来计。另一方面,钉钉出于成本的考虑,资源池中基本没有空余的机器,现有的资源部署密度也比较高,如何腾挪资源在较短的时间内为钉钉接近20个核心集群进行扩容是一个很大的问题。
    (3)极限场景下如何保障系统稳定性与用户体验
    在各种因素制约导致集群无法扩容且系统达已经达到瓶颈时我们能怎么办?有哪些应急手段能用?是否存在一个平衡点,将对用户的影响降到最低?

    3.应对措施

    3.1人员合理化安排

    (1)数据库团队成立疫情期间钉钉业务保障小组
    小组成员包含了数据库团队DBA/数据库内核/CORONA/TDDL/DTS/精卫/NOSQL各产品线同学。根据钉钉业务线进行分工,每个DBA跟进一个业务线,参与高峰期的保障,及时播报线上系统状况与水位,让重保决策人员及时了解系统的状况。对线上出现的问题紧急处理,保证问题在短时间内得到修复。对不合理的业务场景进行优化,保证已知问题只出现一次。参与系统的压测,发现潜在风险点及时修正,对系统容量不够的系统进行及时扩容,在资源满足的情况下让数据库在高峰来临之前已经具备足够的容量。
    (2)数据库团队与钉钉稳定性团队紧密合作
    由于前期资源有限,需要扩容的系统众多,此时钉钉稳定性团队主动站出来帮DBA分担了大量的的压力。他们将数据库的扩容需求根据业务的重要性进行优先级划分,统一扩容需求,DBA根据优先级顺序,结合业务的目标容量进行判断,在有限的资源下有条不紊地进行扩容,保证资源优先用在刀刃上,大大提升了扩容效率。

    3.2资源紧急协调

    疫情突然爆发,所有人都预期流量会增长,但涨多少很难预估,必须要早作准备。为了保证资源不会成为系统扩容的阻力,DBA和云资源团队进行合理规划,短期内通过借用集团上云的机器,同时缩容其他BU数据库集群,凑出400台左右的机器,保证高优先级系统的扩容需求。同时协调云资源进行搬迁,在短短几天内搬迁了300多台机器到钉钉资源池,保证了钉钉所有数据库的扩容需求。
    资源到位后就是检验数据库弹性的时候了,依托于PolarDB-X三节点分布式的部署架构,我们可以较为方便地对原有集群进行在线升级和扩容,对用户影响很低,并且保证数据的一致性。有些场景用户需要从原有集群将数据迁移到分库分表更多的新集群,我们利用DTS搭配成熟的管控平台也能较为流畅地完成。最终可以做到只要有资源,数据库也能具有极致的弹性,满足业务需求。

    3.3应急与优化

    在系统高峰来临之前,数据库团队内部已经准备好紧急预案:

    • 参数降级,调整数据库参数充分发挥数据库能力,提高吞吐
    • 资源降级,调整资源限制,CPU隔离放开及数据库BP大小紧急上调
    • 针对异常SQL,确认影响后紧急限流,或者通过SQL Execute Plan Profile进行紧急干预
    • 全集群流量备库分流,依据压力情况最大可100%读流量切换到备库
    • 准备数据库弱一致脚本,在必要时进一步提高数据库吞吐

    同时结合业务的限流/降级预案保证了很多数据库系统在未知高峰流量到来时的稳定运行。
    但业务限流降低了很多用户的体验,之前业务限流值设置为30QPM/群,表示为每个群在一分钟之内只能发送30条消息,很多时候在1分种的前20s甚至更短时间就已经发出30条消息,在剩下40s以上的时间内用户的体验就是无法使用钉钉。针对这种情况DBA建议减小限流窗口,将限流值30QPM改成30/20S,限流降低了97%,大大改善了用户的体验。

    3.4 DB容量预估及性能分析

    业务上往往通过集群的CPU情况即可大概分析出系统的水位,但是对DB而言不仅是CPU,IO、网络、SQL、锁等等,任何一个组件的瓶颈往往都会成为最终容量的瓶颈。不同的业务模型,往往瓶颈不一样,即使都是查询量较大的业务,有些可能是CPU的瓶颈,有些可能是内存命中率不够导致的瓶颈,有些则是索引设计不合理导致的瓶颈。更复杂的部分在于,有些瓶颈往往不是线性的,可能压力提升2倍还没什么问题,硬件能力都还有富余,但是提升到3倍就直接崩溃。在这种场景下我们如何比较准确地评估DB的容量呢?
    以往我们都是通过经验并和业务方一起进行全链路压测进行DB容量(集群能支撑多少读写)的预估,这种方式有以下几个问题:(1)压测数据集和数据库总量相比往往比较小,DB命中率基本100%,这对于分析有IO的业务模型存在较大误差;(2)成本较大,需要打通上下游整个链路,需较多的人员参与,即使进行全链路压测,真正压到DB端的往往也只是核心的几个接口,无法100%覆盖线上所有的接口,而很多慢SQL往往都来自这些易忽略的接口。
    解决这个痛点问题的方法很容易想到——只要把线上的业务流量全部采集下来回放一遍即可,但实现起来是非常复杂的。我们真正需要的其实是针对DB的一种通用的单链路压测能力,并不依赖上游业务,DB层可以自己进行流量的生成、放大或缩小,甚至具备将事务比例更改后再次压测的能力。从2019年开始,在DBA和达摩院数据库实验室科学家们共同的努力下,我们开发了ClouDBench实现了上述的需求,并在此次的战役中帮助DBA进行容量的评估。效果如下图所示:

    图1:ClouDBench容量评估效果展示.png

    图1:ClouDBench容量评估效果展示

    蓝色是真实业务在某个时刻的性能曲线,绿色是我们采集DB端流量回放出来的性能曲线,可以看出两条曲线在时序上高度拟合,特别是InnoDB内部的指标都非常接近,包括流量的波动。
    当我们能够比较真实地回放出业务的workload,我们即可以对压力进行放大,以此来分析DB的容量,并分析出极限场景下的性能瓶颈,从而进行DB的优化及验证优化效果。ClouDBench目前已经在公共云数据库自治服务Database Autonomy Service(DAS)中灰度上线。

    4.成果及思考

    短短两周内各数据库系统具备了数倍到40倍以上的能力,其中不乏超大型数据库集群,存储空间超过1PB,所有这些都充分证明了阿里云数据库的弹性能力。此次疫情带来的爆发式流量对我们来说是毫无防备的,经历过此役,经验总结下来有以下几点:

    4.1人员组织

    首先在人员组织上,业务和开发要对突发流量具备敏锐的嗅觉,及时发现提早准备,由业务方稳定性负责人成立应急小组,梳理依赖业务以及对应后台系统,将各业务线owner和后台数据库产品owner纳入应急小组。由应急小组统一容量规划、人力配备以及资源协调,实现业务方、后台产品团队、资源团队联动。

    4.2技术架构

    在技术架构上,一方面是要使用具有足够弹性的数据库产品,保证使用的数据库产品有自由扩容和缩容的能力,既要保证流量增大后能扩容,也要保证日常流量时可以缩容。管控等各个运维组件需要在实现自动化运维的同时,对于很多关键操作留有应急开关,确保在一些极端场景下,可以较方便地从自动驾驶切换成手动模式,确保任务平稳高效地运行下去。

    4.3应急手段

    在面对系统瓶劲时,在业务上和数据库产品上都要提前做好预案。在业务上要有降级和限流功能,在系统无法承受压力时,可以降级一部分非核心功能,限制一些次核心功能来保核心业务的正常运行。在数据库产品上需要具有在不扩容的情况下,通过一些优化手段瞬间提升数据库吞吐的能力,更重要的是这些能力需要有较好的兼容性,在不同的部署环境、不同的DB架构下都具有相应的工具和预案。
    另一方面,我们需要有评估和检测预案效果的手段,我们现在可以利用ClouDBench对DB进行容量的分析和预测,但是当前的使用成本还是过高,后续ClouDBench需要更加自动化,降低使用成本,将能力透传给业务的owner,在大促之前,自动进行大量的DB单链路压测,评估系统水位,发现性能瓶颈,优化DB参数,验证预案效果。

    作者:章左中

    阿里云智能数据库产品团队运维专家

    阿里巴巴运维专家,从事数据库领域十年以上,先后在甲骨文(中国)、网易等公司任职,在传统行业和互联网行业都有着丰富的经验。目前在阿里云数据库事业部从事专家服务相关工作,擅长数据库优化、架构设计及异构数据库之间的迁移等。

    作者:陈荣耀

    阿里云智能数据库产品团队运维专家

    曾就职于烽火通信任职Oracle DBA,2015年加入阿里,现任阿里云数据库运维专家,有丰富的Oracle、MySQL运维开发经验,擅长数据库故障诊断、性能调优、稳定性建设。目前主要负责数据库性能压测、流量回放(ClouuDBench )等。

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。

    image.png

    ]]>
    Java创建对象的方法有哪些?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1.运用New 关键字创建实例,这是最常用的创建对象方法。
    2.运用反射,调用Java.lang.Class类当中newInstance方法。只能调用公共的无参构造函数。
    3.运用反射,调用java.lang.reflect.Constructor类中的newInstance方法提供无参或有参实例。除了无参构造器,还可以调用有参数的/私有的/受保护的构造函数。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架Spring、Hibernate、Struts等使用后者的原因。
    4.调用对象的clone方法。必须先实现java.lang.Cloneable接口。
    5.使用序列化和反序列化。必须先实现Serializable接口。
    6.使用unsafe.allocateInstance(class)创建对象。
    本文来源于:奈学开发者社区,如有侵权,请联系我删除~

    ]]>
    Zeppelin SDK :Flink 平台建设的基石-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:章剑锋(简锋),阿里巴巴高级技术专家

    用过 Zeppelin 的人应该比较熟悉 Zeppelin 的 UI,因为 Zeppelin 的主要使用场景都是交互式,用户需要手动来操作。那除了这种手动的方式,还有其他的方式吗?如果你不想用 Zeppelin UI,但又想用 Zeppelin 提交和管理大数据作业 (比如 Flink Job)的能力该怎么办?或者是你在 Zeppelin 里写好了代码,想定时调度起来,或者集成到其他系统里,该怎么办?

    如果你有这样的诉求,那么 Zeppelin Client API (SDK)就是你所需要的东西。

    Zeppelin 简介

    对于不熟悉 Zeppelin 的人,可以用一句话来解释 Zeppelin:大数据引擎的入口,交互式大数据分析平台底座。Zeppelin 最大的特点是连接多种引擎,具有可插拔式,下面这张图例举了一些常用的引擎,当然 Zeppelin 还支持其他很多引擎,这里就不一一例举。

    image3.png

    虽然 Zeppelin 有 Rest API,但是 Zeppelin 的 Rest API 太多,对于很多不熟悉 Zeppelin 的人来说使用 Rest API 门槛太高,所以 Zeppelin 专门开发了一个 Client API (SDK),方便大家做集成。Zeppelin Client API (SDK)分为 2 个层面的的东西(接下来会逐个详细介绍):

    • Zeppelin Client API (Low Level API)
    • Session API (High Level API)

    Zeppelin Client API (Low Level API)

    Zeppelin Client API 可以在 Note 和 Paragraph 的粒度进行操作。你可以先在 notebook 里写好代码 (比如开发阶段在 notebook 里写代码,做测试),然后用 Low Level API 用编程的方式把 Job 跑起来(比如生产阶段把作业定时调度起来)。Zeppelin Client API 最重要的 class 是 ZeppelinClient,也是 Zeppelin Client API 的入口。下面例举几个重要的接口(这些 API 都比较直观,我就不多做解释了)。

    public String createNote(String notePath) throws Exception 
    
    public void deleteNote(String noteId) throws Exception 
    
    public NoteResult executeNote(String noteId) throws Exception 
    
    public NoteResult executeNote(String noteId, 
                                  Map<String, String> parameters) throws Exception
                                  
    public NoteResult queryNoteResult(String noteId) throws Exception 
    
    public NoteResult submitNote(String noteId) throws Exception
    
    public NoteResult submitNote(String noteId, 
                                 Map<String, String> parameters) throws Exception 
                                 
    public NoteResult waitUntilNoteFinished(String noteId) throws Exception
    
    public String addParagraph(String noteId, 
                               String title, 
                               String text) throws Exception
                               
    public void updateParagraph(String noteId, 
                                String paragraphId, 
                                String title, 
                                String text) throws Exception
                                
    public ParagraphResult executeParagraph(String noteId,
                                            String paragraphId,
                                            String sessionId,
                                            Map<String, String> parameters) throws Exception
                                            
    public ParagraphResult submitParagraph(String noteId,
                                           String paragraphId,
                                           String sessionId,
                                           Map<String, String> parameters) throws Exception
                                           
    public void cancelParagraph(String noteId, String paragraphId)
        
    public ParagraphResult queryParagraphResult(String noteId, String paragraphId) 
        
    public ParagraphResult waitUtilParagraphFinish(String noteId, String paragraphId)

    那这些 API 能用来做什么呢?

    一个典型的用途是我们在 Zeppelin 里写好代码,做好测试,然后在第三方系统里集成进来。比如下面的代码就是把 Zeppelin 自带的 Spark Basic Features 用编程的方式跑起来,你不仅可以跑 Zeppelin Note,还可以拿到运行结果 (ParagraphResult)。怎么处理运行结果,就留给你发挥想象的空间吧(可以在你的系统里展示出来,或者可视化出来,或者传给其他系统做消费等等)。

    此外,对于 Dynamic forms(动态控件,比如文本框,下拉框等等),你还可以动态的提供参数,如下面例子里的 maxAge 和 marital。

    ClientConfig clientConfig = new ClientConfig("http://localhost:8080");
    ZeppelinClient zClient = new ZeppelinClient(clientConfig);
    
    String zeppelinVersion = zClient.getVersion();
    System.out.println("Zeppelin version: " + zeppelinVersion);
    
    ParagraphResult paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015259_1403135953");
    System.out.println("Execute the 1st spark tutorial paragraph, paragraph result: " + paragraphResult);
    
    paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150210-015302_1492795503");
    System.out.println("Execute the 2nd spark tutorial paragraph, paragraph result: " + paragraphResult);
    
    Map<String, String> parameters = new HashMap<>();
    parameters.put("maxAge", "40");
    paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150212-145404_867439529", parameters);
    System.out.println("Execute the 3rd spark tutorial paragraph, paragraph result: " + paragraphResult);
    
    parameters = new HashMap<>();
    parameters.put("marital", "married");
    paragraphResult = zClient.executeParagraph("2A94M5J1Z", "20150213-230422_1600658137", parameters);
    System.out.println("Execute the 4th spark tutorial paragraph, paragraph result: " + paragraphResult);

    这下面这张图就是上面我们要 Zeppelin Client API 跑的 Zeppelin 自带的 Spark Basic Features。

    image2.png

    Session API (High Level API)

    Session API 是 Zeppelin 的high level api,Session API 里没有 Note,Paragraph 的概念,粒度是你提交的代码。Session API里最重要的class就是 ZSession,这也是Session API的入口,一个 ZSession 代表一个独立的Zeppelin Interpreter 进程,对于 Flink 来说就是一个独立的 Flink Session Cluster。下面例举一些典型的接口(这些 API 都比较直观,我就不多做解释了)。

    public void start() throws Exception
    
    public void start(MessageHandler messageHandler) throws Exception
    
    public void stop() throws Exception
    
    public ExecuteResult execute(String code) throws Exception
    
    public ExecuteResult execute(String subInterpreter,
                                 Map<< span="">String, String> localProperties,
                                 String code,
                                 StatementMessageHandler messageHandler) throws Exception
    
    public ExecuteResult submit(String code) throws Exception
    
    public ExecuteResult submit(String subInterpreter,
                                Map<< span="">String, String> localProperties,
                                String code,
                                StatementMessageHandler messageHandler) throws Exception
                               
    public void cancel(String statementId) throws Exception
     
    public ExecuteResult queryStatement(String statementId) throws Exception
    
    public ExecuteResult waitUntilFinished(String statementId) throws Exception

    那这个 API 能用来做什么呢? 一个典型的用途是就是我们动态创建 Session (Zeppelin Interpreter 进程),动态的提交运行代码,并拿到运行结果。比如你不想用 Zeppelin 的 UI,要自己做一个 Flink 的开发管理平台,那么你就可以自己做 UI,让用户在 UI 上配置 Flink Job,输入 SQL,然后把所有的这些信息发送到后端,后端调用 ZSession 来运行 Flink Job。

    下面的 Java 代码就是用编程的方式调用了 2 条 Flink SQL 语句,并且在 MyStatementMessageHandler1 和 MyStatementMessageHandler2 中读取源源不断发送过来更新的 SQL 运行结果 (怎么来使用这个结果就靠你的想象力了)。

    需要说明的是像 Flink Interpreter 这种流式结果数据更新是通过 WebSocket 实现的,所以下面的代码里有会有 CompositeMessageHandler,MyStatementMessageHandler1 以及 MyStatementMessageHandler2,这些 MessageHandler 就是用来处理通过 WebSocket 发送过来的流式数据结果。下面是 2 条我们在 Zeppelin 里运行的 Flink SQL。

    image5.png
    image8.png

    接下来我们会用 Zeppelin Session API 来跑着这 2 条 Flink SQL,然后我们会在MyStatementMessageHandler1,MyStatementMessageHandler2 里拿到结果展示出来。

    ZSession session = null;
    try {
        ClientConfig clientConfig = new ClientConfig("http://localhost:8080");
        Map<< span="">String, String> intpProperties = new HashMap<>();
    
        session = ZSession.builder()
            .setClientConfig(clientConfig)
            .setInterpreter("flink")
            .setIntpProperties(intpProperties)
            .build();
    
        // CompositeMessageHandler allow you to add StatementMessageHandler for each statement.
        // otherwise you have to use a global MessageHandler.
        session.start(new CompositeMessageHandler());
        System.out.println("Flink Web UI: " + session.getWeburl());
    
        System.out.println("-----------------------------------------------------------------------------");
        String initCode = IOUtils.toString(FlinkAdvancedExample.class.getResource("/init_stream.scala"));
        ExecuteResult result = session.execute(initCode);
        System.out.println("Job status: " + result.getStatus() + ", data: " + result.getResults().get(0).getData());
    
        // run flink ssql
        Map<< span="">String, String> localProperties = new HashMap<>();
        localProperties.put("type", "update");
        result = session.submit("ssql", localProperties, "select url, count(1) as pv from log group by url",
                                new MyStatementMessageHandler1());
        session.waitUntilFinished(result.getStatementId());
    
        result = session.submit("ssql", localProperties, "select upper(url), count(1) as pv from log group by url",
                                new MyStatementMessageHandler2());
        session.waitUntilFinished(result.getStatementId());
    
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (session != null) {
            try {
                session.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static class MyStatementMessageHandler1 implements StatementMessageHandler {
    
        @Override
        public void onStatementAppendOutput(String statementId, int index, String output) {
            System.out.println("MyStatementMessageHandler1, append output: " + output);
        }
    
        @Override
        public void onStatementUpdateOutput(String statementId, int index, String type, String output) {
            System.out.println("MyStatementMessageHandler1, update output: " + output);
        }
    }
    
    public static class MyStatementMessageHandler2 implements StatementMessageHandler {
    
        @Override
        public void onStatementAppendOutput(String statementId, int index, String output) {
            System.out.println("MyStatementMessageHandler2, append output: " + output);
        }
    
        @Override
        public void onStatementUpdateOutput(String statementId, int index, String type, String output) {
            System.out.println("MyStatementMessageHandler2, update output: " + output);
        }
    }

    除了编程方式跑 Flink Job,这个 Session API 还能给我们带来什么呢?

    在 Zeppelin 里如果你可以通过 %flink.conf 来对你的 Flink Cluster 进行非常丰富的配置,但是 %flink.conf 是纯文本的配置,不熟悉 Flink 的人很容易配错(如下图)。如果你是自己做 Flink 开发平台的话就可以做一个更完整的 UI,用一些下拉框等等把一些配置选项固定下来,用户只要选择就行了,不需要自己输入文本来配置。

    image7.png

    还有下面这类 paragraph 的 local properties 配置,比如 type,template, resumeFromLatestCheckpoint 也是比较容易写错的,同理你可以在自己 UI 里用一些控件把这些选项提前固定下来,而不是让用户输入文本的方式。

    image6.png

    我相信 Zeppelin Client API 还有很多可以发挥和想象的空间,大家脑洞起来吧。

    ▼ 视频演示 ▼

    视频演示链接
    https://v.qq.com/x/page/m3146grr5e1.html

    更多 Flink 技术干货及使用交流可加入 Flink 社区钉钉大群。

    最新钉群二维码.jpeg

    ]]>
    闲鱼直播三周内实现点击率翻倍,我们是这么做到的...-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:闲鱼技术-莫癫

    1. 业务背景

    闲鱼直播业务上线后面临的最大问题是增长问题。闲鱼BI同学分析发现,对比短时观看和长时观看人群,发现两部分人群有较明显的兴趣阶段性差异。
    业务希望在理解直播、主播和用户的基础根据兴趣对头部优质直播精准投放, 放大头部主播马太效应实现直播转化和观看时间的增长。

    2. 目标

    简单概括需要达成两个结果:

    • 在三周内实现精准投放平台,沉淀基础运营平台的基础设施;
    • 业务上保证头部直播间场均转化uv达成一定目标,转换率得到明显提升;

    那么单纯借助算法模型实现优质直播推荐,是否也可以达成业务上的目标?然后现实却是,巧妇难为无米之炊。 直播上线时间短, 播放和观看场次有限, 使得模型的训练没有足够的样本直接去理解用户对直播的兴趣, 平台也未对主播直播内容做强控实现内容的结构化。那么就需要将运营对直播领域经验与BI分析、算法结合, 在理解用户、直播和直播间的基础上,实现对直播间到兴趣人群的投放,并沉淀平台化能力。

    3. 实现方案

    给兴趣人群投放实时直播间的第一步是要实现对人的理解,包括C端用户以及主播的理解,其次是直播的理解。理解的结果最终会以兴趣人群、主播人群的方式与页面资源位关联,形成人(用户)货(直播)场(资源位)的初步匹配。


    用户的理解依赖于用户的特征数据,包括闲鱼用户基础特征,搜索、浏览、发布、交易等商品相关行为记录,互动行为特征和用户兴趣标签特征等。这些特征对实时性要求不高,大部分特征通过离线计算产出,后续通过离线计算方式对不同数据来源的特征归一化。


    用户所有特征会同步到人群圈选平台,通过交并差的方式实现人群圈选,进行人群预览和导出。
    直播投放整体设计.png
    平台整体设计


    圈选的人群数据是以userId和人群Id的映射表方式保存离线,与投放的配置进行联合后得到<用户, 资源位, 主播>的关联关系,而后关系数据会同步到图数据库Igraph,提供给算法在线推荐时查询关联直播实现按兴趣推荐和曝光。受限的是整体的曝光流量有额度的,算法会基于模型,在有限PV额度内对在线直播间实现较优的选择。


    下面详细阐述是怎么实现用户理解直播间投放的。

    用户理解

    对用户理解的常规特征生产不是个难事, 而用户的兴趣标签需要针对闲鱼用户从零开始, 弥补这方面能力的缺失。 兴趣标签主要是通过分析用户历史行为产生的行为文本,找出其与领域标签涉及到词组的关联性。 包含如图商品和帖子的各类行为文本,目前数据在逐渐补充中。

    CDAD267B-747D-4790-9311-909702FD01D9.png


    运营会整理不同领域的关键词词组作为输入, 匹配到关联度高的用户关联上领域标签特征。 要实现兴趣标签的产出, 要解决三个问题: 存储、检索和相关度计算。
    image.png
    兴趣标签产出(方案一)


    如图方案一是最初设想方案, 整体流程如下:

    • 关键词结构化: BI同学完成行为文本明细的处理, 包括数据源归一、去重和UDF处理分词, 并根据关键词频次和预设权重算分。 输出结构化后的用户行为文本明细, 包括用户ID、实体ID、关键词列表和关键词对应的分值列表;
    • 打标规则DSL化:对运营输入的行业兴趣关键词组进行分词后转成数据库可执行的DSL;
    • 兴趣用户DUMP: 执行DSL检索出与输入关键词匹配的结构化行为文本, 进行用户去重, 完成用户兴趣标签关联;
    • 人群圈选: 基于用户兴趣标签和其它特征数据做交并差后导出最终人群, 该步骤是在二方人群圈选平台进行;


    整个方案是可行的, 而且具备很好的灵活性, 离线部分可不断完善和丰富结构化行为文本, 工程测专注于DSL可视化优化和整个数据流的流转提效, 整个平台可以良性迭代进化。 但是该方案确难以实行, 主要存在以下问题:

    • 能给的工期短, 要求2到3周完成所有链路功能上线并支撑业务验证, 实现该方案是几乎不可能的;
    • 存储成本巨大, 测算大概需要30PB的在线存储资源, 这对于一个未验证价值的业务来数也是不可能申请到的;


    有同学也许很快发现, 从文本结构化到检索特定兴趣用户的过程不就是一个可以用搜索引擎实现的业务场景吗? 最大的问题仍然是预算问题, 搭建搜索引擎也是个不小的成本,而且从搜索引擎dump大量数据存在着严重的性能问题,同时也无法支持BI同学在整个流程中进行优化。
    4E3F537D-6CCD-4000-9CF7-DC3DD4641255.png
    搜索引擎基本流程


    在线方案是比较理想的, 可以实现运营利用自己的行业经验自助完成兴趣标签关联和人群圈选。由于上述客观条件限制, 最终我们选择了离线关联用户和兴趣标签的方式, 快速接入部分兴趣标签, 而后逐步推进在线方案的方式。 这里得益于BI同学全面的能力, 完成了“离线搜索引擎”, 以及未雨绸缪沉淀了部分用户兴趣标签。 这样整体方案就是这样的:

    • 离线处理非结构化文本,通过去重、分词和算法得到结构化文本(该步骤与方案一相同);
    • 整理领域标签关联的关键词词组
    • 离线计算方式检索匹配关键词词组的用户


    方案二的最大弊端就是通用性没方案一高,每个兴趣标签的产出需要BI开发,只能满足T+1的实时性。但也一些优点,离线存储成本低,离线计算可支持自定义复杂UDF。离线部分更详细的介绍可以参考数据团队的兴趣标签体系实现介绍。
    image.png
    兴趣标签产出(方案二)

    投放实现

    投放分为离线和在线两部分, 运营维护的投放配置存储在RDB (关系型数据库), 需要同步到数据仓库, 离线计算完成用户与兴趣主播关系关联, 形成<用户,兴趣主播列表>关系。 关联的数据同步到在线图关系数据库, 提供算法在兴趣主播中推荐。 整个数据链路需要自动流转, 尽可能及时:

    • 在线配置无法做到实时同步到离线, 目前每一个小时调度一次, 达到准时时要求;
    • 离线任务之间通过依赖任务驱动, 基本能满足准实时行要求,并每次全量更新“用户主播兴趣关系”新增新分区,同时增加与新分区时间一致的done分区;
    • 离线数据同步到在线图数据库是基于数据交换组件, 会定时检查离线表done分区, 有新done分区则会通过同步消息机制进行对应相同时间分区的全量数据更新;

    image.png

    4. 首页效果

    在三周不到的时间,完整链路的平台实现并上线,运营人群圈选、投放配置可在分钟级内完成上线。
    对部分领域的头部直播在首页进行试投放后,效果明显:

    • 所有头部直播间,UV点击数远超目标;
    • 对比大盘,试投放大部分领域PV和UV的点击转化率得到显著提升,最高达到倍数提升;

    5. 展望

    整个项目由于时间比较短, 实现的是兴趣直播投放功能的最小集合, 以支持快速验证并得到较好反馈和结果。在此雏形上,未来会逐渐完善和丰富其能力:

    • 在对接BI兴趣标签的基础上, 需要不断丰富对接兴趣标签等各维度的特征数据能力,同时支持运营同学自助产出通用兴趣标签以及其它特征;
    • 丰富对资源位的投放能力支持,并具备多维度AB方案和多指标通用报表分析能力。能支持更多业务的快速尝试、快速反馈和快速调整;
    • 沉淀和抽象出核心链路, 不局限于支持直播业务, 可以平台化支持更多的社区和非社区业务。同时在理解用户兴趣的基础, 更好的支持理解内容, 实现内容结构化, 实现用户和兴趣内容的低成本运营;


    ~~

    ]]>
    郑荣:世界的香格里拉——从“藏在深闺无人识”到“网红旅游目的地” | 阿里CIO学院名人堂-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 7月中旬,迪庆藏族自治州文化和旅游局宣传推广及对外交流与合作科科长郑荣作为阿里创新学院的学员走进阿里巴巴,参与了为期两天的数智化升级培训班。

    image.png

    带着打造“世界的香格里拉”的决心,郑荣在参会期间认真听取了阿里巴巴关于数字化营销、数字化管理、数字化转型等相关话题的分享,并对此有了更为系统的认识。会后,郑荣接受阿里CIO学院新媒体访谈,分享了他对文旅行业数字化转型的未来构想。

    郑荣先生认为文旅行业的数字化转型主要有三方面的切入点,分别是政府管理、文旅企业以及游客需求。以这三个切入点为引,他为我们分享了迪庆文旅产业以往发展的一些经验,指出了未来在数字化转型这条道路上文旅行业可能会面临的挑战。

    在郑荣先生的构想里,文旅行业未来发展离不开文旅产品和业务的创新,只有创新才能带来发展,让“世界的香格里拉”这一天生具备网红基因的旅游胜地更好地走向全国、走向世界。

    用数字化技术要让传统文旅资源活起来

    阿里CIO学院:郑先生您好,请问这次走进阿里巴巴参与两天的学习交流给您带来的真实感受是什么?

    郑荣:本次学习交流首先让我了解了阿里巴巴的企业文化和业务生态。通过聆听与会专家大咖们的演讲,我对数字化营销、数字化管理、数字化转型和创新等有了系统性的认识,了解到互联网科技发展的最新动态。这次学习对于我今后工作的开展大有裨益,我们将会更深入地探索迪庆文旅产业发展的数字化转型和创新,进一步擦亮“世界的香格里拉”这一品牌。

    阿里CIO学院:您认为文旅行业在数字化转型过程中的切入点是什么?在数字化转型的过程中可能会面临哪些挑战?

    郑荣:我认为文旅行业数字化转型的切入点主要在三个方面。

    一是政府管理,通过数字技术赋能行政部门的公共服务和行业监督,通过智能化管理来构建新服务和新监管的格局。

    二是文旅企业,通过数字技术变革传统文旅商业模式和业态,让传统的文化和旅游资源“活起来”。

    让传统文旅资源活起来,关键是在于融合,包括文化和旅游的融合、资源和数字化技术的融合,从而打造文旅行业新业态。以迪庆州来说,全州现有县级以上非遗项目200余项,但是更多的是“藏在深闺无人识”。随着旅游业的发展,我们提出了开发“非遗之旅”的策略,让游客在欣赏自然风光之外,可以选择体验尼西黑陶制作、唐卡绘画、藏香制作等延续千年的藏族传统文化。同时,以具有民族代表性的非遗项目为载体,创意设计研发文创旅游产品,也是文化和旅游融合最佳途径之一。

    而借助最前沿的数字化技术,以文化和旅游资源打造线上虚拟景区、线上虚拟博物馆,将使得文旅资源没有障碍地触达更广泛的人群。

    三是游客需求。数字化技术正在不断地影响着游客的行为和体验认知,游客的个性化需求越来越明显。随着移动互联网技术发展,我们已经进入了个性化定制时代,旅游业也正从传统大众观光游时代走向个性化消费时代转变。

    个性化需求的表现有很多市场细分,比如追求新鲜和新奇、寻求新潮和心跳、偏爱自驾和小团体出游,讲究游玩的主题和深度游,还有追求豪华和野趣等。

    应对旅游的个性化需求,首先我们要加强大数据收集、分析和运用,通过大数据分析形成旅游消费者画像,从而形成人群细分,挖掘不同人群的个性化需求,在此基础上进行旅游线路产品的设计和开发。数字化技术的应用可以提供更受游客青睐的交互式、沉浸式的旅行体验。

    关于旅游业的数字化转型的挑战,我觉得首要是人才的瓶颈,尤其是在我们云南迪庆州,数字化技术人才是相当稀缺的,这也是制约我们实施数字化转型和创新的最大难题。其次,文旅数字化转型不能狭隘地停留在单一的政府部门或企业行动上,而应该从整个文旅行业发展角度出发,要有整体性的数字化转型规划和推进策略。

    举例而言,在数字化转型过程中,迪庆州文旅局不断强化在旅游管理和服务上的数字化运用,同时不断地引导文旅企业搭上数字化转型的列车,推动主要景区加强数字信息化技术的应用。

    从目前我们的经验来说,就是要避免政府一手包办,文旅部门更多扮演的是牵头引导的角色,更主要的是发挥文旅企业的积极能动性,让他们在项目运作中感受到切实的利益和收获,不断提升文旅企业对数字化转型的认知。比如:2019年迪庆州文旅局与抖音合作开展的“世界的香格里拉”抖音全国互动挑战赛,由文旅局牵头落实,旅游企业和主要景区共同承担费用,同时也是活动的主角取得了不错的成效。

    文旅产业数字化转型营销要先行

    阿里CIO学院:您认为在数字化转型的路上,文旅行业产品与业务创新应该如何变化?数据智能将会对文旅行业的行业格局带来怎样的变化?

    郑荣:文旅产品和业务创新表现在以下几个方面:

    首先是要加强数字化技术的应用。从VR、AR到人工智能、大数据、云计算、物联网、5G等,文旅企业应该通过这些技术手段的应用,不断推动传统文化和旅游资源深度融合,进行文旅产品的革新,推出更具有交互性和沉浸式的文旅新产品。

    其次,数字化转型目前对于迪庆文旅企业的改变主要体现在营销宣传和景区服务上。目前越来越多的景区已经装载了智慧讲解、智慧门禁、智慧停车场等服务设施,使得景区的服务水平和游客体验有了一定的提升。其次,文旅企业通过互联网新媒体开展品牌、产品营销宣传的意识不断增强,可以让游客消费者多渠道多维度的了解认识迪庆的多民族传统文化和壮丽的自然景观。对于旅游景区而言,应该通过数字化技术的运用,打造“智慧景区”,提升景区的整体服务水平,给广大游客创造更好的旅行体验。

    再次,文旅产品的营销上,要注重挖掘游客的新体验和新需求,并借用数字技术手段进行人群细分、目标精准的营销推广。近年来,迪庆不断推进非物质文化遗产的数字化记录工作,利用图文、音视频等手段进行非遗资源采集,构建非遗数据库。

    数字化采集记录将非遗项目立体留存下来,可以让更多人熟悉、了解、喜欢迪庆的民族文化瑰宝,通过互联网等传播途径,也让非遗文化传播到更广阔的时空。

    数字化转型和数字化营销取得明显提升

    阿里CIO学院:在数字化转型这条道路上,有什么成功或失败的经验能和我们分享的吗?

    郑荣:近年来,迪庆州文旅局一直在探索文旅行业的数字化转型,逐步积累了一些经验。自2018年开始,迪庆州全面推进智慧景区建设,包括智慧门禁系统、智慧停车场、智慧厕所、电子导游等,景区的数字化水平得到明显提升。

    image.png

    数字化营销宣传也是迪庆州文旅局近年重点发力的方向,迪庆文旅与人民网、新华网、中国网、携程、抖音、微信、微博、新浪、网易以及海外的Google(谷歌)、Facebook(脸书)、YouTube(优兔)等众多平台开展合作,以互联网新媒体营销将香格里拉打造成为全国“网红旅游目的地”。在目前的互联网新媒体合作中,最成功的案例当属2019年与抖音合作开展的“世界的香格里拉”抖音全国互动挑战赛,本次挑战赛在云南省首开先河,以迪庆州文旅局牵头,联合迪庆州内主要景区共同参与。

    本次挑战赛期间有30余人次的知名网红来到迪庆,为迪庆文旅拍摄创意短视频。挑战赛覆盖网民超过5000万+人次,优质短视频参与数量超过50000+,挑战赛话题播放量达7亿次,视频点赞超过1000万+。

    虽然取得了一些成绩,但对于迪庆文旅行业长远发展来讲,无论是政府职能部门还是行业企业,实施数字化转型和创新依然任重道远。

    迪庆藏族自治州文化和旅游局介绍

    image.png

    2019年3月11日,新组建的迪庆藏族自治州文化和旅游局正式挂牌,香格里拉市、德钦县、维西县等县市级文化和旅游局相继挂牌,标志着迪庆州文化和旅游迈出了深度融合的步伐。

    十三五”期间,州委、州政府高度重视文化和旅游融合发展,主要领导亲自部署,分管领导全力以赴,狠抓落实,在全州上下形成了重视文化和旅游融合、大抓文化和旅游发展的良好氛围。

    通过不断优化文化旅游营造软环境,吸引了一批优秀的民营企业在迪庆的长期旅游投资建设,如巴拉格宗、松赞精品山居、独克宗花巷等一批重量级旅游项目均是由民间企业投资并成功运营。

    近年来,迪庆州把 “世界的香格里拉”品牌宣传和旅游产品营销有机结合,创新方式,积极参加国际国内旅游宣传促销,组织开展昆明、上海、北京、成都等主要客源市场的旅游宣传营销活动,大力宣传推广迪庆冬季旅游产品和线路,冬季节庆活动和非遗产品,开通“世界的香格里拉”文化和旅游官方抖音号。

    迪庆州邀请中央权威媒体以及旅游卫视、旅游报、文化报、经济网、中国日报、新华网、人民网等家媒体到迪庆开展以“旅游扶贫”为主题采风活动。通过线上线下、传统媒体与新媒体相结合,形成综合旅游品牌影响力、旅游产品吸引力、旅游价格竞争力的立体宣传营销体系,扩大了迪庆州在国内外的影响力,促进了客源市场的稳步增长。

    据了解,随着旅游产业的不断发展,旅游产业链不断扩大,截至目前我州旅游从业人员达12.6万人,占全州总人口的30.7%,其中旅游直接从业人员达2.6万人,间接从业人员达10万人,切实解决了就业难问题。二是景区反哺带动旅游扶贫。到2018年,全州景区直接及间接带动村民共涉及5600余户,28000多人,每年总收入7000多万,平均每户年增收12600多元,人均达2520元左右。

    文章来源:阿里飞天CIO学堂微信公众号

    名人堂

    名人堂是阿里CIO学院打造的一档大伽访谈栏目,每周一期。以推动企业创新与数智化升级为愿景,通过采访行业顶尖客户,帮你更好地了解和思考企业数字化转型中可能面临的挑战,梳理行业痛点和方法路径,从而相互滋养,共同成长。

    更多文章

    点击查看青岛华通集团智能研究院明磊:拥抱数字化转型正在成为CIO头等大事 | 阿里CIO学院名人堂>>>

    点击查看ERIC,ZHANG:地产业数字化转型按下“加速键”美好人居正当时 | 阿里CIO学院名人堂>>>

    点击查看李锋:联接数智化生态,打造“数字化越秀” | 阿里CIO学院名人堂>>>

    点击查看龙湘君:基金行业奏响数字化转型五部曲 | 阿里CIO学院名人堂>>>

    点击查看段晓力:从一个小目标的对立 到万个小目标的融合 | 阿里CIO学院名人堂>>>

    点击查看贾坤:扶贫基金事业借数字化转型助力决胜脱贫攻坚战 | 阿里CIO学院名人堂>>>

    ]]>
    中华保险荣获IDC大奖,阿里云、蚂蚁金融科技成“助推器”-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 8月28日,知名调研机构IDC中国举办线上的“2020 IDC中国未来金融论坛暨颁奖典礼”,中华保险“理赔服务资源管理系统”凭借创新的金融业务场景和技术应用,荣获中国金融行业技术应用场景创新奖。本次获奖意味着中华保险的数字化创新能力受到权威机构的认可与肯定。

    0005.jpg

    作为全球知名的IT市场研究机构,IDC一直密切关注着中国金融行业的发展与变革。今年是IDC主办的第六届中国未来金融论坛暨颁奖典礼,基于金融云、普惠金融、智慧网点等金融行业技术应用场景,共有近150个优秀项目参与评选。此次获奖的“理赔服务资源管理系统”借助金融云分布式架构和AI等技术,以数据为驱动,分析渠道和客户,灵活调配资源,集中资源响应优质客户的谈判,进而改善车商渠道业务的经营状况。

    IDC评价,该项目通过重塑送修服务流程与管理模式,在提高理赔客服工作效率的同时,实际的送修成功率有效提升,进而促进车商渠道业务的增长,实现车商业务的精细化管理和高质量发展。同时,迎合线上化理赔发展趋势,解决客户等待时间过长、服务不连贯、体验不佳等问题,推动传统经营模型向数字化智能化转型。

    中华保险始创于1986年7月15日,2002年,经国务院批复同意,冠名“中华”,成为全国唯一一家以“中华”冠名的国有控股保险公司,市场规模位居国内财险市场第五,农险业务规模稳居全国第二。当前,中华保险正在加速数字化转型进程,希望通过建设新一代核心业务系统,推进保险业务朝着线上化、数字化、智能化方向发展。

    据了解,中华保险新一代核心业务系统采用阿里云全套专有云平台、数据中台、业务中台与金融科技产品,并创新性地引入金融云公共平台。整个方案包含“飞天”云计算操作系统蚂蚁金融分布式架构 SOFAStack分布式数据库OceanBase金融数据智能平台金融核心套件bPaaS保险专家服务mPaaS移动开发平台金融钉钉等一系列产品技术与服务,助力中华保险加速“数字中华”建设。

    “我们希望未来能变成一家真正以客户为中心,科技和创新驱动的保险公司”,中华财险相关负责人表示,对保险行业来讲,这次与阿里云合作可能是业内第一次有保险公司完完整整地去重构业务模式、业务理念以及IT配套。

    目前,包括蚂蚁移动开发平台mPaaS、金融级分布式架构SOFAStack、企业级分布式关系数据库OceanBase等在内的产品和解决方案正通过阿里云新金融统一对外输出,服务各种类型的金融机构。未来,还会有越来越多的蚂蚁技术产品通过阿里云新金融对外输出,全力助推金融客户完成金融数字化转型。

    蚂蚁SOFAStack白皮书已于近期发布,点击此处即可获取。

    ]]>
    开源直播系统源码,为什么一定要使用PHP语言开发-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 进行开源直播系统源码的开发工作时,开发者们遇到的最多问题就是使用什么语言开发的,回答大多是PHP语言。对于没有开发经验的人来说,很难理解PHP语言到底代表着什么,又有哪些作用。

    直播2.jpg

    其实,PHP是一种开源脚本语言。语法吸收了C语言、java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域。
    PHP由内核Zend引擎和扩展层组成,PHP内核负责处理请求、完成文件流错误处理等操作,Zend引擎可以将PHP程序文件转换成刻在虚拟机上运行的机器语言,扩展层提供一些应用层操作需要的函数类库等,比如数组和MySQL数据库的操作等。
    开源直播系统源码Zend引擎是用C语言实现的,将PHP代码通过词法语法解析成可执行的opcode并实现相应的处理方法和基本的数据结构进行内存分配和管理等,对外提供相应的可供调用的API方法。Zend引擎是PHP的核心,所有的外围功能都是围绕它实现的。扩展层通过组件的方式提供各种基础服务、内置函数,标准库都是通过它实现的。用户也可以编写自己开源直播系统源码的扩展来实现特定的需求。服务端应用编程接口,通过一系列钩子函数使得PHP可以和外围交互数据。我们平时开源直播系统源码编写的PHP程序就是通过不同的SAPI方式得到不同的应用模式,如通过web实现的web应用和命令下运行的脚本等。

    直播11.jpg

    作为开源直播系统的开发语言,PHP语言的优势在于:
    1.PHP的开发成本低,入门学习快,语法也相对简单,并且提供丰富的类库
    2.PHP与Linux、Nginx、Apache、MySQL可以方便快捷的搭建出一套系统,支持直接调用系统命令,用代码完成许多操作Linux的工作

    直播12.jpg

    3.PHP支持使用多种数据库,其中与MySQL的结合是最为流行的,PHP提供三种链接MySQL的扩展。PDO扩展是PHP退出的连接MySQL和其他类型的数据库的一种统一解决方案。可移植性很高,使用它可以灵活方便的切换不同类型的数据库,而且不需要变动更多的代码
    4.PHP是解释执行的脚本语言,写完程序后,可以立即执行,所以它的开发效率很高
    声明:以上内容为云豹科技作者本人原创,未经作者本人同意,禁止转载,否则将追究相关法律责任

    ]]>
    想提升专业技能?阿里云9月ECS和AI千人训练营为你助力!!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 阿里云高校计划首次 ECS千人训练营即将来袭,9月高校计划ECS和AI训练营等你来参加!获取云计算和AI项目经验和提升技术技能!UP! UP!

    1598866732841-5a27afab-d8f9-43f7-9861-d84ee934c82a.png

    阿里云高校计划团队精心设计的ECS课程,从0开始学习云计算,7天3个项目带你快速积累云计算项目经验;AI训练营五天三个云上AI项目,手把手带你入门视觉AI

    ECS高校训练营:入门班和进阶班
    每班只招500人,入门+进阶共招1000人
    AI训练营仅招100人


    ECS训练营参与进阶班的同学完成打卡+任务+作业就发ACA资格!阿里奖品多多!同时,这次的AI训练营仅招100人,高校的同学们一定不要错过这次机会!

    记好这些时间呦
    ECS训练营
    报名参营
    • 报名开始时间:9月17日 10:00(可能提前开始)
    • 报名结束时间:9月20日 24:00
    上课打卡
    • 打卡开始时间:9月23日
    • 打卡结束时间:9月28日


    AI训练营

    报名参营
    报名开始时间:9月17日 10:00
    报名结束时间:9月20日 24:00
    上课打卡
    打卡开始时间:9月24日
    打卡结束时间:9月28日

    敲重点!
    ECS训练营是什么?


    ECS 7天实践训练营由阿里云高校计划发起,旨在帮助对云计算感兴趣的同学更好地开展云上实践、积累项目经验,为云计算人才成长助力。训练营由阿里云大学资深讲师和技术大咖联手策划,陪你动手上云实践。

    ECS训练营有什么?


    ECS训练营有阿里云高校计划精心准备的课程,建网站做项目,能让你快速积累项目经验,完成作业和打卡就可以获得699元阿里云ACA官方认证考试资格。7天,每天30分钟,云计算技能全方位提升,为你的升学就业加分!

    训练营路线怎么选?


    根据你的技术学习水平进行选择。入门班/新手路线适合0基础云计算小白,进阶班适合学过“新手路线” or 有一定云计算基础以及想要考ACA的同学,如果你想为你的简历增加闪光点一定要来报名参加ECS训练营。

    AI训练营是什么?


    视觉AI训练营四位导师带你入门视觉AI,体验AI技术。五天时间完成身份证识别、人脸+表情识别、车辆保险等云上项目,快速入门视觉AI,为求学升职加分。你的AI第一课,阿里云陪你完成!

    阿里云高校计划训练营为什么值得来?
    团队学习,进步更快

    同学们根据自己的技术水平报名了ECS训练营入门班和进阶班和AI训练营报名参营后,将与来自全国高校的同学们一起组团进行7天的ECS训练营和5天的AI训练营学习。同学们会竞选班长和组长,成班成组进行学习,每天要按时交作业——学习笔记,会有严格的学习监督机制,督促你学习成长。

    开营结营直播助力学习


    为了帮助同学们了解训练营和认识同学们,我们会在9.22日召开ECS训练营开营直播和9.23进行AI训练营开营直播,并在9月28日进行结营直播。直播中我们会邀请往期参营和考过ACA的同学为大家分享经验,解答问题。开营直播更有两轮抽奖送出阿里周边。

    精美丰厚奖品等你拿


    开营直播两轮抽奖送阿里周边好礼
    精选笔记送好礼:好的学习笔记可以帮助你学习和成长,我们会挑选好的学习笔记,大家共同-学习成长,被精选笔记的同学会收到阿里周边奖品。

    头条抽奖 3个阿里云棒球帽

    DOWLO_f09f703c3121987d23dd0385f46cb3b0 (2).jpg

    微博抽奖:3个阿里云棒球帽

    DOWLO_f09f703c3121987d23dd0385f46cb3b0 (2).jpg

    预热直播抽奖 7个 阿里云公仔 有奖问答送三本《弹性计算》书籍
    开营直播抽奖 7个 阿里云公仔 有奖问答送三本《弹性计算》书籍

    IMG_8256.JPG

    阿里云公仔

    1598958068042-d2b38e34-bf52-4fb5-8b56-1d4547b38ec0.png


    《弹性计算》



    参营:打卡+完成作业 送ACA考试资格(进阶班)
    优秀笔记获奖者 送阿里云笔记本
    ECS 入门班送12本阿里云笔记本
    ECS 进阶班送12本阿里云笔记本
    AI训练营送6本阿里云笔记本


    IMG_8238.JPG
    IMG_8239.JPG

    阿里精美笔记本

    结营直播:
    优秀营员(群里表现积极活跃+每日打卡+按时完成作业)三营共评选20人 送阿里棒球帽

    DOWLO_f09f703c3121987d23dd0385f46cb3b0 (2).jpg

    优秀组长和班长 评选3人 送阿里云徽章


    lALPGqGobAkQy1_NAXbNAZc_407_374.png


    想看往期同学参营感想,点这里:
    https://mp.weixin.qq.com/s/T399HWt86EgM5UvEAMM_eg

    报名及收取最新开营消息见下图

    步骤.jpg

    附上课程路线
    · ECS 7天训练营(新人路线)
    o 9月23日:阿里云云服务器ECS介绍https://developer.aliyun.com/lesson_46_6557#_6557
    o 9月24日: ECS服务器购买和准备https://developer.aliyun.com/lesson_71_18415#_18415
    o 9月25日: ECS云服务器新手上路https://developer.aliyun.com/adc/scenario/410e5b6a852f4b4b88bf74bf4c197a57
    o 9月26日:安装Putty工具远程连接ECS服务器 https://developer.aliyun.com/lesson_71_18416#_18416
    o 9月27日:安装Linux服务器面板管理工具 https://developer.aliyun.com/lesson_71_18417#_18417
    o 9月28日:快速搭建LAMP环境
    https://developer.aliyun.com/adc/scenario/6869de098ad44fc8a1560a1836a7c5f2
    · ECS 7天训练营(进阶路线)
    o 9月23日:基于ECS搭建FTP服务
    https://developer.aliyun.com/adc/scenario/74b64efe414c47fbaf305957a7fb458b
    o 9月24日:快速搭建Docker环境
    https://developer.aliyun.com/adc/scenario/9fd79b8711984e309f20d82bc65a26fa
    o 9月25日:ECS+SLB 负载均衡实践https://developer.aliyun.com/adc/scenario/d82547cc96d74b43a3027e0dc018cf1c
    o 9月26日:使用ECS和PolarDB搭建门户网站https://developer.aliyun.com/adc/scenario/a7161434e6cd499e9e26f2e62d0242fb
    o 9月27日:搭建个人Leanote云笔记本 https://developer.aliyun.com/adc/scenario/b1ac9deecdb143469de985471b440aa7
    o 9月28日:案例分享——钉钉 https://developer.aliyun.com/lesson_46_1538#_1538

    AI训练营
    · 打卡开始时间:9月24日
    · 打卡结束时间:9月28日

    课程路线
    · 9月24日:达摩院视觉AI技术应用探索https://developer.aliyun.com/live/2761?spm=a2c6h.14795454.J_4770946980.3.3a065794QAk5ML
    · 9月25日:身份证识别系统搭建 https://developer.aliyun.com/live/2762?spm=a2c6h.14795454.J_4770946980.4.3a065794HT7Kl7
    · 9月26日:电子相册搭建(人脸、表情识别)https://developer.aliyun.com/live/2763?spm=a2c6h.14795454.J_4770946980.5.3a065794ramEfR
    · 9月27日:作业日-车辆保险应用
    · 9月28日:创意日-你的图像识别项目

    ]]>
    Apache的代码居然也有"bug"?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 引言

    二狗:二胖快醒醒,赶紧看看刚才报警邮件,你上次写的保存用户接口耗时(《二胖的参数校验坎坷之路》)大大上升,赶紧排查下原因。
    二胖:好的,马上看,内心戏可十足(心里却在抱怨,大中午的搅我发财美梦,刚刚梦见我买的股票又涨停了就被叫醒了)。牢骚归牢骚,自己的问题还是得看啊,毕竟是自己写的bug,含着泪也要把它修复掉。二胖对分析这种问题还是得心应手的,毕竟已经是久经职场的老油条了。

    测试环境复现问题

    二胖首先通过内部的监控工具看了下这段时间的网络是否正常,以及cpu的使用情况、数据库的耗时等,这些指标看起来都是正常的,唯一稍微有点区别的是这段时间流量上涨了一些,肯定又是公司花钱搞营销砸广告了。接着二胖又通过cat(大众点评开源监控工具)分析了几个请求,每个阶段的耗时看下来都ok。卧槽这可咋办列居然难倒二胖了,如果生产环境问题可以在测试环境复现就好了,这样解觉问题就简单多了。生产不是流量上涨了一些吗?那测试环境来压测一把吧,二胖果断的下载了一个jmeter(压测工具)在测试环境进行了一把疯狂的压测,果然出现了和生产一样的问题。能够复现问题就好,这样离解决问题就近了一大步。

    arthas定位问题

    问题是复现了,接下来就是找出接口比较耗时的地方了。一般我们找接口耗时较长的地方,都是通过记录日志打印每一步的耗时。这是比较常见做法,不过二胖记得上次部门技术大拿“二狗”分享过一个神器arthas可以输出方法路径上的每个节点上耗时。苦于一直没有机会拿它来用于实际操作,今天终于可以拿它来好好练手了。安装什么的就不介绍了,这个官网都写的比较详细,并且文档也是中文的,非常容易上手。下面我们就来使用下arthas吧。
    启动成功的界面
    在这里插入图片描述
    下面我们根据arthas提供的trace命令来看看接口的耗时都是在哪里。
    在这里插入图片描述我们从上面可以看出主要耗时是集中在 org.apache.commons.beanutils.BeanUtils#copyProperties这个方法上面的,不就一个实体之间的属性赋值转换吗,需要这么耗时这么久吗?不科学啊,apache提供的方法还能这么low吗?带着这些问题我们看看其他提供的属性拷贝的工具类效率如何。

    使用JMH对常见属性赋值操作性能比较

    • 使用getset方法复制。
    • cglibBeanCopier
    • SpringBeanUtils
    • apacheBeanUtils
    • MapStruct
      下面我们就来对上面这些操作来进行一波性能比较。

    编写下面的测试类。

    /**
     * @author:
     * @Date: 2020/7/11
     * @Description:
     */
    @BenchmarkMode(Mode.AverageTime)
    @Warmup(iterations = 3, time = 1)
    @Measurement(iterations = 5, time = 5)
    @Threads(6)
    @Fork(1)
    @State(value = Scope.Benchmark)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public class BeanCopyTest {
        @Param(value = {"1","10","100"})
        private int count;
    
        public UserBO bo;
    
        public  BeanCopier copier;
    
        @Setup(Level.Trial) // 初始化方法,在全部Benchmark运行之前进行
        public void init() {
            copier = BeanCopier.create(UserBO.class, UserVO.class, false);
            bo = new UserBO();
            bo.setUserName("java金融");
            bo.setAge(1);
            bo.setIdCard("88888888");
            bo.setEmail("java金融@qq.com");
        }
    
    
        public static void main(String[] args) throws RunnerException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
           Options opt = new OptionsBuilder().include(BeanCopyTest.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build();
            new Runner(opt).run();
    
        }
    
        /**
         * 使用mapStruct来操作
         */
        @Benchmark
        public void mapStruct() {
            for (int i = 1; i <= count; i++) {
                UserVO vo = UserMapping.INSTANCE.converter(bo);
            }
        }
    
        /**
         * 手动set和Get
         */
        @Benchmark
        public void setAndGet() {
            for (int i = 1; i <= count; i++) {
                UserVO userVO = new UserVO();
                userVO.setUserName(bo.getUserName());
                userVO.setEmail(bo.getEmail());
                userVO.setSex(bo.getSex());
                userVO.setIdCard(bo.getIdCard());
                userVO.setAge(bo.getAge());
            }
        }
    
        /**
         * 使用cglib的copy方法
         */
        @Benchmark
        public void cglibBeanCopier() {
            for (int i = 1; i <= count; i++) {
                UserVO vo = new UserVO();
                copier.copy(bo, vo, null);
            }
        }
    
        /**
         * 使用spring提供的copyProperties方法
         */
        @Benchmark
        public void springBeanUtils() {
            for (int i = 1; i <= count; i++) {
                UserVO vo = new UserVO();
                BeanUtils.copyProperties(bo, vo);
            }
        }
    
        /**
         * 使用apache的copyProperties方法
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        @Benchmark
        public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException {
            for (int i = 1; i <= count; i++) {
                UserVO vo = new UserVO();
                org.apache.commons.beanutils.BeanUtils.copyProperties(vo, bo);
            }
        }

    最后的测试结果如下所示:

    Benchmark                     (count)  Mode  Cnt          Score          Error  Units
    BeanCopyTest.apacheBeanUtils        1  avgt    5    2462103.419 ±  2292830.495  ns/op
    BeanCopyTest.apacheBeanUtils       10  avgt    5   21025926.689 ± 11254755.603  ns/op
    BeanCopyTest.apacheBeanUtils      100  avgt    5  193235312.113 ± 37929707.246  ns/op
    BeanCopyTest.cglibBeanCopier        1  avgt    5          4.936 ±        1.187  ns/op
    BeanCopyTest.cglibBeanCopier       10  avgt    5          4.820 ±        1.963  ns/op
    BeanCopyTest.cglibBeanCopier      100  avgt    5          4.269 ±        0.890  ns/op
    BeanCopyTest.mapStruct              1  avgt    5          4.809 ±        1.720  ns/op
    BeanCopyTest.mapStruct             10  avgt    5          4.947 ±        1.320  ns/op
    BeanCopyTest.mapStruct            100  avgt    5          4.440 ±        1.191  ns/op
    BeanCopyTest.setAndGet              1  avgt    5          3.780 ±        1.785  ns/op
    BeanCopyTest.setAndGet             10  avgt    5          3.930 ±        1.788  ns/op
    BeanCopyTest.setAndGet            100  avgt    5          4.069 ±        2.181  ns/op
    BeanCopyTest.springBeanUtils        1  avgt    5       1190.563 ±      165.574  ns/op
    BeanCopyTest.springBeanUtils       10  avgt    5      10887.244 ±     1228.026  ns/op
    BeanCopyTest.springBeanUtils      100  avgt    5     109686.562 ±     7485.261  ns/op

    在这里插入图片描述

    • 从上述结论中我们可以发现性能最好的是排名 用getset方法复制,其次是mapStructcglib的BeanCopier,再接着是Spring的beanUtils,最后的是apache的BeanUtils
    • 如果对上述测试性能感兴趣的话,代码都已上传到github上可自行下载运行对比下结果。代码地址
    • 关于对JMH的使用就不介绍了,感兴趣的可自行谷歌。不过如果要进行性能比较的话,真心推荐使用下,结果可以通过导出json文件然后生成图表。

    为什么apacheBeanUtils性能最差

    apacheBeanUtilsspringbeanUtils都是底层都是使用反射来进行赋值的,为什么apacheBeanUtils的性能要差一大截列。源码之下无秘密,下面我们来看看这个方法的源码。
    在这里插入图片描述
    Apache BeanUtils 打印了大量的日志、以及各种转换、类型的判断等等导致性能变差。

    • springbeanUtil直接使用反射省,干净利索,核心代码见下图。
      在这里插入图片描述
    • 其实在《阿里巴巴开发手册》(可在公众号【java金融】回复“泰山”获取)里面也有说明属性的copy避免使用apcheBeanUtils
      在这里插入图片描述
    • 如果生产环境已经大量使用Apache BeanUtils的话需要替换spring BeanUtils的话需要注意下他们两个虽然提供的方法都是copyProperties但是他们的参数是反的,这点需要注意下,不要直接换个引入的包名完事。

    总结

    • 实际使用中的话一般是不会使用getset方法复制,容易漏掉属性并且也是一个体力活。推荐使用mapStruct,在编译过程中,MapStruct将生成该接口的实现,并且它还可以实现不同名字的映射,比如可以把name映射到username,灵活性比较高。
    • 二胖感觉今天收获满满啊,一下学到了jmeterarthasJMH三个软件的使用。

    结束

    • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
    • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
    • 感谢您的阅读,十分欢迎并感谢您的关注。
      在这里插入图片描述
    ]]>
    开源 Flink + 实时计算 Flink 版训练营学习资料汇总-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Apache Flink:全球领先的开源大数据计算引擎

    Apache Flink 是一个开源的分布式大数据处理引擎, 可对有限数据流和无限数据流进行有状态计算。作为 Apache 软件基金会 (ASF) 顶级项目之一,Flink 在流处理方面具有绝对的优势,提供高吞吐、低延时的计算能力, Exactly-once 语义保证数据的准确性,亚秒级别的处理延迟确保业务的快速响应。

    作为快速发展的新一代大数据引擎,Flink 本身的架构优势也吸引着越来越多的开源爱好者投入到社区的建设来。 截止到 2020 年 7 月,社区的 star 数达到 13600+ ,contributor 数达到 718,有 22989 次 commits。伴随着社区的快速发展,Flink 也成为类似阿里巴巴、腾讯、字节跳动、滴滴、美团点评等知名公司建设流处理平台的首选。

    【推荐阅读】

    Flink 社区技术发展风向标

    重磅!Apache Flink 1.11 功能前瞻抢先看!

    更易用!Hive 集成弯道超车

    Hive 终于等来了 Flink

    Flink PMC 联合各大厂用人主管助你升职加薪

    Flink 面试指南

    【企业案例】

    OPPO 实时数仓揭秘:从顶层设计实现离线与实时的平滑迁移
    单日总数据处理量超 10 万亿,峰值超每秒 3 亿
    bilibili 实时平台的架构与实践
    基于 Flink 的 bilibili Saber 实时计算平台
    美团点评基于 Flink 的实时数仓平台实践
    深度解析美团点评实时数仓案例

    【电子书】

    《零基础入门:从 0 到 1 学会 Apache Flink》
    30 天成长为 Flink 大神
    《Apache Flink 年度最佳实践》
    国内外一线大厂超大规模最佳实践案例合集

    阿里云实时计算 Flink 版

    实时计算 Flink版(Alibaba Cloud Realtime Compute for Apache Flink,Powered by Ververica))是阿里云提供的基于 Apache Flink 构建的企业级、高性能实时大数据处理系统,由Apache Flink创始团队官方出品。在 PB 级别的数据集上可以支持亚秒级别的处理延时,赋能用户标准实时数据处理流程和行业解决方案;在支持 Datastream API 作业开发的同时,提供了完整的SQL语义,使得 BI 场景下的开发变得更加简单;丰富的上下游 connector 保证了与用户已使用的大数据组件无缝对接;智能作业调优和诊断功能进一步简化了用户的开发和使用。

    实时计算 Flink版在 Apache Flink 核心功能的基础上还增强了企业用户所关注的集群稳定、性能优化、安全控制、系统监控和作业管理等。阿里云实时计算团队目前是全球最大、拥有 Committer 数量最多、专业性最强的 Flink 团队,为实时计算用户提供企业级的管理和咨询服务。2019 年 6 月,由数据中心联盟发起的大数据产品能力评测结果权威发布,阿里云实时计算 Flink版通过最新制定的分布式流处理平台基础能力评测,成为国内首批通过流计算产品能力评测的产品,并被数据中心联盟圈定为国内大数据流计算基础平台第一梯队。2020 年在国际知名咨询调研公司Forrester 的测评中,实时计算 Flink版成为中国唯一进入Forrester象限的实时流计算产品。

    【独享月度特惠】开通实时计算产品:

    master型号4核16GB+master数量1+slave型号4核16GB+slave数量2,计费周期1个月,详情:

    https://common-buy.aliyun.com/?spm=a2c0j.14094430.1053885.bnt1.307976feKRl1Au&commodityCode=blinkonecs#/buy
    Tips:开通成功算打卡成功

    解决方案

    [实时计算 Flink:基于 Apache Flink 构建的大数据计算平台(附白皮书)
    ](https://developer.aliyun.com/article/770133?groupCode=sc)

    Tips:完成PDF阅读,算打卡成功)

    最佳实践

    实时计算 Flink 版 最佳实践

    Tips:完成文章阅读,算打卡成功

    ]]>
    进击的Kubernetes调度系统(三):支持批任务的Binpack Scheduling Fri, 20 Jun 2025 02:20:33 +0800 作者:王庆璨 张凯

    进击的Kubernetes调度系统(一):Scheduling Framework
    进击的Kubernetes调度系统(二):支持批任务的Coscheduling/Gang scheduling
    进击的Kubernetes调度系统(三):支持批任务的Binpack Scheduling

    前言

    本系列的前两篇《进击的Kubernetes调度系统 (一):Scheduling Framework》进击的 Kubernetes 调度系统(二):支持批任务的 Coscheduling/Gang scheduling 分别介绍了Kubernetes Scheduling Framework和如何通过扩展Scheduling Framework实现Coscheduling/Gang scheduling调度策略。当我们的批任务作业在集群里边运行起来之后,随后要关注的就是资源的利用率。特别是对于GPU卡的价格昂贵,不希望有资源的浪费。本文将介绍在批任务的调度过程中如何通过Binpack的方式,减少资源碎片,提升GPU的利用率。

    为什么需要Binpack功能?

    Kubernetes默认开启的资源调度策略是LeastRequestedPriority,消耗的资源最少的节点会优先被调度,使得整体集群的资源使用在所有节点之间分配地相对均匀。但是这种调度策略往往也会在单个节点上产生较多资源碎片。

    下面拿一个简单的例子来说明这种问题。如下图所示,资源在节点之间平均使用,所以每个节点使用3个GPU卡,则两个节点各剩余1GPU的资源。这是有申请2GPU的新作业,提交到调度器,则因为无法提供足够的资源,导致调度失败。


    1587092661680-55bf016c-e041-47b8-8bc4-2b49db8f90ff.png
    ]]> 解读Knative 0.17.0版本特性 Fri, 20 Jun 2025 02:20:33 +0800 前言

    Knative 0.17.0 版本已于近期发布,对于 Knative v0.17.0 版本新特性,我们进行解读,让大家对 Knative 新版本快速了解。
    Knative 0.17.0 支持 k8s 最小支持版本为:1.16。

    Serving

    Autoscaling-自动扩缩容

    • 1)支持Revision初始POD数设置
      在0.17.0之前的版本中,创建新的Revision(即使新Revision流量比例为0)默认会先创建出 1 个POD实例,新的POD创建完成之后,如果没有流量,缩容为 0。 其实大多数场景下,新的Revision创建完成之后不需要立刻创建出POD。从0.17.0开始引入 inital scale 参数,可以指定新的 Revision 为 0。

    inital scale 参数可以通过注释设置:

    autoscaling.internal.knative.dev/initialScale

    核心 API

    组件HA高可用支持

    从0.17.0版本开始,默认开启组件HA,可以通过配置参数‘--disable-ha’ 关闭HA。

    Knative Service 特性支持

    • 1) 对 affinity, nodeSelector, tolerations 和 securitycontext 进行了支持。对于该特性的支持比较意外,因为该特性很早就被提出过,但开始的社区的反馈是 serverless(no server) 场景下不应该关心节点的调度。目前来看结合实际的使用需求场景,最终社区做出了妥协。
    • 2) 新增全局最大扩容Pod配置参数max-value,当没有设置autoscaling.knative.dev/maxScale值时,使用该全局配置作为最大扩容Pod。
    • 3) 优化 Revision 版本回收机制。新增Revision版本最大数maximum限制,同时支持禁用基于时间的Revision回收策略。

    Networking-网络

    KIngress

    • 1)最大超时设置。对于gRPC stream处理超时的问题,默认超时时间调整为了48小时。
    • 2)支持域名重写(RewriteHost)。通过该特性可以实现自定义域名。以Istio实现为例,自定义域名‘vanity.com’通过‘rewrite’实现重定向。
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: vanity-ingress
    spec:
      gateways:
      - knative-serving/knative-ingress-gateway
      hosts:
      - vanity.com
      http:
      - match:
        - authority:
            prefix: vanity.com
          gateways:
          - knative-serving/knative-ingress-gateway
        rewrite:
          authority: helloworld-go.default.example.com
        route:
        - destination:
            host: cluster-local-gateway.istio-system.svc.cluster.local
            port:
              number: 80

    Eventing

    PingSource优化

    0.17.0版本 PingSource 支持时区(time zone)设置

    事件处理优化

    在 In Memory Channel 和 Multi-Tenant Channel 中发送事件失败时,支持重试机制。

    总结

    Knative 0.17.0 版本中引入了版本初始化实例数设置以及对节点调度参数的支持(nodeSelector etc.), 表明Knative社区会更多的关注实际应用场景,相信后续有更多实用特性会提供出来。欢迎有兴趣的同学一起交流。

    欢迎加入 Knative 交流群

    image.png

    ]]>
    【漏洞预警】jackson-databind 反序列化远程代码执行漏洞(CVE-2020-24616等) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月27日,阿里云应急响应中心监测到jackson-databind官方发布安全通告披露jackson-databind < 2.9.10.6存在反序列化远程代码执行漏洞(CVE-2020-24616等)。利用漏洞可导致远程执行服务器命令,官方git已发布公告说明,请使用到jackson-databind jar组件的用户尽快升级至安全版本。


    漏洞描述

    jackson-databind是一套开源java高性能JSON处理器,受影响版本的jackson-databind中由于缺少黑名单类,如br.com.anteros:Anteros-DBCP,可导致攻击者实现远程代码执行,其相关CVE号为CVE-2020-24616,漏洞需要相关jar组件才能成功利用,影响面适中。此次版本升级官方还修复了多处利用链,包含org.arrahtec:profiler-core、com.nqadmin.rowset:jdbcrowsetimpl、com.pastdev.httpcomponents:configuration等。阿里云应急响应中心提醒jackson-databind用户尽快采取安全措施阻止漏洞攻击。


    风险评级

    CVE-2020-8840 中危


    影响版本

    jackson-databind < 2.9.10.6


    安全版本

    jackson-databind >= 2.9.10.6


    安全建议

    以下任意一种方法均可实现漏洞修复

    1、针对使用到jackson-databind组件的web服务升级jackson-databind组件至安全版本:https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.9.10.6/

    https://github.com/FasterXML/jackson-databind/releases

    2、针对无法升级jackson-databind的,排查并将相关jar组件从应用依赖中移除可阻止漏洞攻击(可能会导致应用不可用风险)


    相关链接

    https://github.com/FasterXML/jackson-databind/issues?q=label%3ACVE+is%3Aclosed



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.08.27

    ]]>
    【升级】8月20日Neustar注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Neustar注册局维护通知】

    维护时间:北京时间 2020年8月20日 16:00 - 8月21日 01:00

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .biz 域名的注册、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、转入、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月26日DDoS高防(国际)升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【DDoS高防(国际)】【升级通知】

    升级窗口:北京时间2020年8月26日 10:00 - 22:00

    升级内容:清洗网络增加英国节点
    升级影响:升级后国际高防线路将新增回源网段,具体网段信息如下所示。如果您当前正在使用国际高防线路并且在服务器侧有相关针对源IP的访问控制策略,请及时更新白名单放行下述回源网段,避免误拦截造成业务影响。

    国际高防线路新增回源网段信息如下:

    170.33.88.0/24 

    170.33.92.0/24

    170.33.93.0/24 

    170.33.90.0/24

    8.208.75.0/26

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【漏洞预警】通达OA 多个高危漏洞 Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月20日,阿里云应急响应中心监测到通达OA官方发布安全更新,其中修复了多个高危漏洞。


    漏洞描述

    通达OA(Office Anywhere网络智能办公系统)是由北京通达信科科技有限公司自主研发的协同办公自动化软件。近日通达OA官方发布安全更新,其中修复了多个高危漏洞。攻击者通过文件删除漏洞,以及配合早期版本的后台文件上传漏洞,可以获取服务器系统权限。阿里云应急响应中心提醒通达OA用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    文件删除漏洞:通达OA V11.6

    任意文件上传:通达OA < V11.7

    结合任意用户登录利用链:通达OA < V11.5


    安全建议

    通达OA官方已经发布相应安全加固程序,请根据当前OA版本选择所对应的程序文件,运行前请先做好备份。

    安全更新下载地址:https://www.tongda2000.com/download/sp2019.php


    相关链接

    https://www.tongda2000.com/



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.8.20

    ]]>
    【漏洞预警】宝塔面板数据库管理未授权访问漏洞 Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月23日,阿里云应急响应中心监测到宝塔官方发布安全更新,修复了一处未授权访问漏洞。


    漏洞描述

    宝塔面板是一款使用方便、功能强大且终身免费的服务器管理软件,支持Linux与Windows系统。近日宝塔面板官方发布安全更新,修复了一处高危漏洞。攻击者通过访问特定路径,可以直接访问到phpmyadmin数据库管理界面,并可借此获取服务器系统权限。阿里云应急响应中心提醒宝塔面板用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    Linux版本 7.4.2版本

    Linux测试版本 7.5.14版本

    Windows版 6.8版本


    安全建议

    宝塔面板官方已经发布相应安全加固程序,请根据当前版本选择所对应的程序文件,运行前请先做好备份。

    安全更新下载地址:https://www.bt.cn/bbs/thread-54644-1-1.html


    相关链接

    https://mp.weixin.qq.com/s/XSDbyU-5TNzFCrp0sb0qKw



    阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单联系反馈。

    阿里云应急响应中心

    2020.8.23

    ]]>
    【漏洞预警】Qemu 虚拟机逃逸漏洞(CVE-2020-14364) Fri, 20 Jun 2025 02:20:33 +0800

    ISC2020第八届互联网安全大会上,QEMU-KVM虚拟机的0day漏洞(虚拟机逃逸)被公开。该漏洞可越界读写某一个堆之后0xffffffff(4 GB内存)的内容,可实现完整的虚拟机逃逸。阿里云已于2019年12月完成该漏洞的修复,云上主机已不受该漏洞影响。


    2020年8月24日,qemu 官方更新了安全补丁修复该漏洞,漏洞编号:CVE-2020-14364,https://xenbits.xen.org/xsa/advisory-335.html


    漏洞详情

    2019年11月17日天府杯国际网络安全大赛,第一次暴露出QEMU-KVM虚拟机的0day安全漏洞。2020年08月13日,ISC2020第八届互联网安全大会上,该漏洞被公开。该漏洞可越界读写某一个堆之后0xffffffff(4 GB内存)的内容,可实现完整的虚拟机逃逸,最终在宿主机中执行任意代码,造成较为严重的信息泄露。经阿里云工程师分析后判定,该漏洞是 Qemu 历史上最严重的虚拟机逃逸漏洞,影响到绝大部分使用 OpenStack 的云厂商。


    解决方法

    阿里云已于2019年12月完成该漏洞的修复,云上主机无需做修复操作。

    如果您有相关需求或反馈,请提交工单联系阿里云。


    相关链接

    https://help.aliyun.com/document_detail/179131.html

    阿里云应急响应中心

    2020.8.24

    ]]>
    Agile Development敏捷开发——培养敏捷精神-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Manifesto敏捷开发宣言

    Offical 官方:

    Manifesto for Agile Software Development

    We are uncovering better ways of developing
    software by doing it and helping others do it.
    Through this work we have come to value:

    • Individuals and interactions over processes and tools
    • Working software over comprehensive documentation
    • Customer collaboration over contract negotiation
    • Responding to change over following a plan

    That is, while there is value in the items on
    the right, we value the items on the left more.

    Translation 翻译:

    敏捷开发宣言

    我们正在探索更好的发展方式通过做它和帮助别人做它。
    通过这项工作,我们认识到:

    • 个人和交互胜过过程和工具
    • 工作软件优于综合文档
    • 客户合作胜过合同谈判
    • 响应变化而不是遵循计划

    虽然右边有价值,但是左项具有更大的价值。

    Wrong Name Comprehend 错误的命名理解

    agile 翻译出自牛津英汉汉英词典

    ① (lithe) 敏捷的 ‹person, animal, movement›; (fit) 灵活的

    ② attributive(mentally acute) 机敏的 ‹mind, intellect›*

    敏捷,拼音是mǐn jié,解释出自百度百科

    意思指反应(多指动作或言行)迅速快捷。如:敏捷地跳上敞篷车,敏捷地翻身上马,敏捷地躲过攻击。出自《汉书·酷吏传·严延年》:“延年为人短小精悍,敏捷於事。”

    通过上述两个翻译的对比我们不难看出关于敏捷一词,中英的理解其实是完全不同的,在汉语中敏捷一词表达的更重要的意为迅速

    然而在英语的语境中,当aglie形容人时,意也指,然而当aglie用来形容思维时,更注重的是机敏灵活。

    所以这里更加取决于无论boss也好,leader也好,亦或是程序员也好,大家的共识里面认为软件开发究竟是一个体力活,还是一个头脑风暴,思维实现的过程。在进入公司的第一天,leader便告诉了我一个团队信条,“听懂做到讲明白”,"Jump to white board from keyboard",只有从根本上认可自己做的工作是思维工作而非重复劳动,才能深刻的理解到"敏捷"一词的意思。

    可是如果我们将《敏捷开发》翻译为《机敏开发》/《灵活开发》,可能敏捷开发对于当今的业界影响力,应该会大打折扣。毕竟敏捷开发这4个字就会得到boss或者leader的认可,因为快速高效正是中小型公司或是大型公司需要贯穿整个企业的工作方式。

    回归到敏捷开发真正的含义:一种一人为本,团队合作,快速响应变化和可工作的软件作为宗旨的开发方法。

    敏捷开发的高效并不在于一味的快速,敏捷开发推崇的是一个正向的循环,通过产品的不断迭代,每一次的code review (代码评审),product review(生产发布评审),Requirements review(需求评审),Standing meeting Daily(每日站会)来实现快速高效,协作同步。团队经历了不断的磨合,自然而然的最后可以回归到boss和leader的需求:快速高效。

    所以敏捷开发对于一个研发团队而言,可以拉平大家的信息理解,让大家可以随时确认大家的设计思想是一致的,而不是在中间的理解中出现了理解偏差,使得最后的整个产品根本不满足于市场。相信各位经历过程序返工的同僚,都可以想象真正的低效缓慢正是做的事不被boss/leader认可,不被市场认可,疲于返工,从而团队共识分崩离析,进入一个无限的恶性循环。

    Waterfall Development VS Agile Development

    在网络上提到瀑布式开发,总会给人一种二极管的感觉,更有一种踩一捧一的感觉,通过踩瀑布式开发来捧敏捷开发。这对于瀑布式开发个人认为是完全不公平的,难道瀑布式开发就真的被敏捷开发给完爆了吗?其实不然

    Waterfall Development 瀑布式开发

    (图片出自百度图片)

    1. Requirements/analysis(需求分析/调研)
    2. Design(设计)
    3. Coding(编码)
    4. Testing(测试)
    5. Maintenance(项目运维)

    Agile Development 敏捷开发

    图片出自敏捷开发入门教程-阮一峰

    1. 项目启动(Initial planing)
    2. 需求分析(requirements analysis)
    3. 设计(design)
    4. 编码(coding)
    5. 测试(testing)
    6. 部署和评估(deployment / evaluation)

    contrast对比

    瀑布开发 敏捷开发
    工作模式 以文为本:
    1. 文档将贯穿整个项目生命周期,从(需求分析,软件设计,编码设计,测试用例,运维部署)每个阶段都将输出相应的文档
    2. 每一个阶段的文档输出后都将作为下一个阶段的输入
    3. 以文档为基准,交流的作用不一定有文档可靠
    4. 产品上线后即可满足用户90%的需求,之后只需要投入少量的运维成本
    以人文本
    1. 强调了团队的合作,需要团队的共同协作与配合,实时拉平大家的理解,当然并不是说文档不重要,而是团队协作以人为本更加重要
    2. 迭代是产品的核心思维,产品的第一次上线可能结果并不乐观,但是需要根据市场的反馈来进行迭代,与各个团队的员工拉平市场的需求,进行产品的迭代
    3. 不断的迭代迭代,最终将产品进入一个正向循环的模式
    项目周期 第一次发布通常会花上半年及以上的时间,产品上线后投入少量人手运维即可 通常第一次发布会在两个月内,之后不断的迭代,实现符合市场的产品
    优点 1. 目标明确,各个阶段大家只需要完成自己的工作,最后输出相应的文档即可
    2. 产品上线后,即可撤出大量人手投入新的项目开发中,只需要少量人员运维即可
    3. 在团队中调配相应资源时非常的明确,因为各个阶段的目标是明确的
    1. 在经过了不断的迭代之后,输出的产品一定是符合市场需求的
    2. 灵活性高,如果出现了巨大的市场变革,亦或是紧急的需求,只需要加入新一轮迭代即可
    3. 以人为本,调动整个项目各方(包括市场)的参与,理想形态下,开发模式将不再是分配制,而是各个成员主动承担工作
    缺点 1.风险高,各个阶段如果有一个地方出错,那么将作为输入传入下一阶段,最终做出的产品或许完全无法满足市场的需求,需要回炉重做
    2.如果出现市场变革基本等于项目死亡,紧急的需求的插入也将影响整个软件的开发,甚至可能需要从头设计
    3.会出现人员空滞的情况,因为各个阶段都需要等待上一阶段的输出结果后才会展开工作
    以人为本,就是敏捷开发最大的风险。你是否可以相信团队中的每一个人,大家是否拥有着敏捷开发的五个价值观(出自敏捷开发Scrum):
    - 承诺 – 愿意对目标做出承诺
    - 专注– 把你的心思和能力都用到你承诺的工作上去
    - 开放– 项目中的一切开放给每个成员看,大家共同分享
    - 尊重– 尊重团队中的每个人,就事论事而不是有色眼镜看人
    - 勇气– 有勇气做出承诺,履行承诺,接受别人的尊重
    是否可以对每一个团队成员说出 “你永远可以相信我”
    适用项目 需求明确,在开发周期中(一年内)定不会出现巨大市场变革 互联网项目,敏捷开发可以响应互联网快速的发展变更
    需要急速上线,抢占市场的项目

    通过上述的对比我们可以看出,并不是任何产品都适合敏捷开发,敏捷开发并不是一个万灵药,如果在不适当的项目跟中强行使用敏捷开发,反而会让它变成愚笨开发,因为我们会将明确的需求反复沟通,反而降低了研发效率。更重要的是,你团队中的成员是否适合敏捷开发,以人文本即为敏捷开发的核心思想。

    培养敏捷精神

    敏捷开发的核心是以人为本,关于敏捷开发的流程,具体实现,在网络上已经有很多了,大家随时都可以看到网络上给出的示例,以及请专业的敏捷团队来公司进行培训。本篇文章的主要主题是如何培养敏捷的精神 ,接下来将会根据我从书籍文档中,工作经验中学到的敏捷精神,希望阅读了的你,不会感觉文章是一篇口水话鸡汤,而是能让你感同身受的文章。

    1. Blame can't help 指责没有帮助任何事

    指责并不会修复bug更不会帮助任何事。遇到问题我们的反应如果都是去思考解决问题的方案,而不是解决导致问题的人,才是一个正向的循环。

    勇敢的承认自己不知道的答案,与团队的各个成员讨论。如果出现了一个重大的错误,应该被当做是一个学习的机会而不是落井下石的机会,团队中应该互相帮助,而不是落井下石。特别是如果有一个东西,大部分人都知道是坑,此时去诱导一个并不清楚的人跳下去,就算自己逃过了一劫,那么在这样的团队中,怎能知道下一个坑自己是否能够躲过呢?如果次次都躲过,你并不会成为受人尊敬的人,而是行业老油条,是受人背地里唾弃的人,别人又将如何尊重你?

    你可能会说,如果真的有团队成员一而再,再而三的对团队造成了负面的影响,那么此时他应该离开团队,但我们并不该周而复始的指责他。

    2. Don't blindly seek of speed 不要盲目追求速度

    在各个公司,亦或是各个团队中,都会有一套编码规范,如果盲目的追求速度,我们就会在我们的代码中增加许多的魔法值,漏掉注释。最后这段代码将会晦涩难懂,甚至作者本身都不一定能看懂如何实现。

    追寻代码规范,不盲目追求速度,对自己新增的变量/对象命名负责,对自己的代码块注释负责,才是一个研发最基本的素养,而不是将代码工作想象为一场赛马比赛。如果遇到自己不确定的问题,特别是性能/设计上的问题,一定要向同事请教。

    我上周便向我的leader请教了

    假如同一个业务涉及到三张关联表。
    做某个接口服务时,是三表联查,直接得出结果,还是三个表分别查做隔离,组合成最后结果。

    leader和研发同事们都给出了自己的理解与解决方案,令我受益匪浅,或许在这类性能问题发生之前,我都不会再去因为这个问题而痛苦,当问题发生时,我相信我的团队会和我一起著力于解问题,得到更加适合的解决方案

    以下是我综合同事和leader给出的解决方案得到的答案:

    1. 根据阿里巴巴规范:首先超过三张表关联强制禁止join
    2. 如果单表查询能够使用缓存或者复用,建议使用单表
    3. 虽然多表关联能够省去很多业务代码,但是绝大多数情况并不能带来性能上的很大提升,数据库关联查询也会增加SQL的复杂度
    4. 单表查询还便于以后扩展分库分表,SQL层优化空间大,业务层对于大数据量可使用一定算法解决性能问题
    5. 在C端项目中,我们要完全避免联合查询,在B端项目中,我们可以尽可能避免联合查询,在G端项目中,我们完全无法避免联合查询

    3. Focus on issue,not person对事不对人

    人人都懂的道理,人人都喝过的鸡汤,却很难做到的事。做不到对事不对人,只会给整个团队带来一种消极,人人自危,人人想落井下石的氛围。

    Negativity kills Innovation (消极扼杀创新):

    我们每个人都可以想到一些极好的创新想法,同样也有可能萌生一些特别憨憨的想法。如果提出自己的观点前,担心自己被嘲笑,甚至被批评,你将会扼杀掉自己的创意。任何一个出色的产品都需要大量的创新力和市场洞察力,分享各自的观点,融合彼此的优良创新设计,才可以做出一款好的产品。

    “你不需要很出色才能起步,但你必须起步才能变得出色”——莱斯布朗

    “能欣赏自己并不接受的想法,表明你的头脑足够有学识”——亚里士多德

    以下的话说给作者自己听:

    如果你连起步的勇气都没有,请问出色和你这辈子能有什么关系呢?

    在抒发每个人的idea的过程中,我们一定会遇到无休止的讨论,甚至最后从头脑风暴变成了扯淡大会也不是没有可能,如何避免这类情况的发生呢?

    • 设定最终期限:在最终期限时,如果没能确定到最好的方案,我们需要的是寻找最适合的方案,而不是无休止的讨论,需要落地思维碰撞
    • 逆向思维:寻找方案/idea的缺点,如果最终有一个方案的缺点都是大家可以接受的,那么这个方案是不是就是最好的方案呢?
    • 仲裁人:仲裁人需要做到绝对公正,如果有人从头脑风暴变成了扯淡大会,那么需要仲裁人出来制止,维持会议正常进行
    • 支持决定:落地的方案需要执行下去,除非能够证明方案是错的,否则请尊重大家制定的方案

    4. Brave 勇气

    人类的赞歌就是勇气的赞歌 出自《JOJO的奇妙冒险》

    勇气,勇敢这是一个大家都从小听到大的概念,但是想要拥有勇气,拥有勇敢,真的是相当困难的事,做一个有勇气的人,这句话真的是能让人耳朵都听出茧来,又更何况在一家企业中这么做呢?

    勇气会让人感觉有点不自在,鼓足勇气需要魄力。有些时候,它是扫除障碍的唯一途径,否则问题将进一步恶化下去,最终会变成,明明知道了错误,却不断的将错就错。鼓起你的勇气,这能让你从恐惧中解脱出来。当然这是在你已经知道了正确答案的情况下,如果你此时的工作正是一个前无古人后无来者的工作,你作为这艘船上的船员,自己都不知道下一个领域会是哪里的时候,此时你的勇气究竟是破釜沉舟的航行下去,还是下船,这是一个哲学问题?如果你正在做一件没人知道答案的事,很羡慕你,我也想如同你一样。

    5.Tracking changes 跟踪变化

    互联网的进步实在是太快了,个人感触最深的例子就是jQuery与三大前端框架,这里甚至给了我一种断层式进步的感觉。再说到java,如果关注java的新特性,我们会发现从java8的Lambda到jdk14中间出现的各类语法糖,都会发现java正在不断的向函数式编程靠拢,js中也引入了很多面向对象的概念,每一门语言都在不断的去完善自身的特性,如果我们闭门造车,无法追随行业的变化,和在监狱中的囚犯又有什么区别呢?在电影《肖申克的救赎》中,当囚犯回归到城市时因为完全无法适应现代的变化,而在旅店选择了自杀,而主角因为不断的写信,阅读,始终与时代接轨,最终完成了自己的救赎。

    监狱的可怕并不在于关押了人很多年,更可怕的事情是,也许你自己成为了自己心灵的囚徒,是自己内心的监狱禁锢了你自己。

    想起来前段时间在网络中看到的一段很有意思的话

    任何在我出生时已经有的科技都是稀松平常的世界本来秩序的一部分。

    任何在我15-35岁之间诞生的科技都是将会改变世界的革命性产物。

    任何在我35岁之后诞生的科技都是违反自然规律要遭天谴的。

    ——英国科幻作家道格拉斯·亚当斯

    任何比我早出生10年及以上的人都是裹步不前的老顽固。

    任何出生时间和我相差10年以内的人都是这个社会的精英,中流砥柱。

    任何比我晚出生10年及以上的人都是无可救药垮掉的一代。

    ——纳什·沃夏尔·硕德

    囚徒究竟是被监狱囚禁了?还是自己囚禁了自己?

    如何做到跟踪变化呢?

    • 迭代和增量式学习:

    知识投资也是一样。你需要定期投资最低限度的时间量。养成一种习惯,如果需要的话。躲到你的家庭办公室里去或者走进有无线网络的咖啡厅。并非每期学习都同样富有成效,但是只要定期安排学习,长期来看一定会成功。如果你一直在等待空闲时间或者等待灵感的突现,那么它永远都不会发生。

    安排自己定期的学习时间,听到不熟悉的术语/新鲜事物时,记录下它,计划时间深入研究它

    • 逛逛论坛,知乎,掘金社区,简书,github:社区永远是最潮流的地方,书籍的潮流程度将会低于社区,但是书籍的严谨程度高于社区
    • 如饥似渴的阅读:reading,reading,reading!

    我扑在书本上,就像饥饿的人扑在面包上!——高尔基

    • 跟踪技术变化:不需要精通每一门技术,但是我们需要了解行业动态,规划自己的职业生涯

    个人脑洞:如果有一天出现了一门完全取代了java的语言,我会怎么做?我能否提前发现技术更迭的征兆,成为技术革命的受益者呢?

    6. Investment your team 对你的团队投资

    总是要成为你所在的那个乐队中最差的乐手。如果你是乐队中最好的乐手,就需要重新选择乐队了 ——爵士吉他手Pat Methany

    追赶团队,和团队中的各个成员形成正向的互补,有勇气抱有开放的心态与团队中的成员分享,尊重团队中的成员给出的意见,承担团队的任务。构建属于团队的学习平台。如果你认为分享知识你就亏大了,除非你是一个国家级的科研工作者,需要签订严格的保密协议,否则我们需要思考自己是不是井底之蛙呢?当你视若瑰宝的东西有一天被人发现所有人都知道了,甚至有更好的方案时,你那是的脸会不会像被人用皮鞋踢过一样,开始无能狂怒呢?最后愤然感慨“任何在我35岁之后诞生的科技都是违反自然规律要遭天谴的。”任何比我晚出生10年及以上的人都是无可救药垮掉的一代。

    7.Reduce 减法

    参考文档

    《高效程序员的45个习惯——敏捷开发修炼之道》

    敏捷开发入门教程-阮一峰

    github地址

    ]]>
    Why Oauth2.0?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Guide

    本文仅作为Oauth2使用参考,如果本身已经接触过Oauth2.0,希望本篇文档能帮你打开一些思路,能够让你清晰的选择是否要使用Oauth2.0

    如果没有了解过Oauth2.0,请跳转阮一峰Oauth2.0讲解

    Base Q&A

    Question

    如果你最近正在参与关于是否要使用Oauth2.0的会议,你是否会在会议中不断的回到三个很基础的问题:

    • 什么是Oauth2.0?
    • Oauth2.0存在的意义是什么?
    • 为什么要使用Oauth2.0?

    Answer

    1. 首先我们需要明确一点Oauth2.0是协议,它包括了如下特征:

      • 授权协议:

      授权是Oauth2.0思想的核心,其目的就在于拥有了授权,就能够通过授权取得权限范围内的受保护资源

      • 委托协议:

      当用户授权第三方软件获取部分用户数据时,其本质就是资源所有者委托了第三方进行资源的使用

      • 安全协议:

      安全问题永远是互联网最为核心的问题,Oauth2.0协议本身就推荐了相当多的安全处理方案,因此说Oauth2.0也是安全协议

    更需要永远记住的一个观点是,Oauth2.0不是一种身份认证协议,如果思维不能明确Oauth2.0的协议边界线,那么接下来的讨论我们将会不断的回到上述提到的3个问题。

    1. OAuth 2.0 就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据
    2. OAuth 2.0 一词中的 “Auth” 表示 “授权”,字母 “O” 是 Open 的简称,表示 “开放”。简而言之Oauth的核心思想在于开放授权,因此在使用Oauth协议时,我们也不禁需要确认自己的远大目标,是否要做一个开放授权的系统

    谈到Oauth2.0就不得不提到Oauth1.0:

    在 OAuth 1.0 的时候,它有个 “很大的愿望” 就是想用一套授权机制来应对现实中的所有场景,比如 Web 应用场景、移动 App 应用场景、官方应用场景等等,但是这些场景并不是完全相同的。比如官方应用场景,你说还需要让用户来授权吗?如果需要,始终使用一套授权机制给用户带来的体验,是好还是坏呢?到了 OAuth 2.0 的时候,就解决了 OAuth 1.0 面临的这种“尴尬”。OAuth 2.0 不再局限于一种授权机制,它扩充了授权许可机制类型,有了授权码许可机制、客户端凭据机制、资源拥有者凭据机制和隐式许可机制。这样的 OAuth 机制就能够很灵活地适应现实中的各种场景,比如移动应用的场景、官方应用的场景,等等。

    具体的Oauth1.0 vs Oauth2.0比较可以参考下方参考文档,这里暂不赘述

    总结下来使用Oauth2.0的原因就是其改善了自身的授权机制,优化了安全处理,采用了多种授权的形式,以此来适应如今互联网在授权中会遇到的各类问题

    4 characters

    Oauth2.0中最重要的四个角色

    • 资源拥有者
    • 受保护资源服务器
    • 授权服务
    • 第三方应用

    不管我们在开放授权中的各类形式讨论,我们所有的讨论应该基于这4个角色,而不是在于使用Oauth2.0的哪种模式,因为模式的选择,其本质需要考虑所谓第三方应用是否可信,大家所处的是内部局域网,还是纯正的互联网第三方

    Todo Question

    希望上述的讲解可以让大家同步的是Oauth2.0的基本问题,如同做一个SaaS平台一样,我们需要的是先讨论什么是SaaS,什么是租户隔离,而不是刚开始就讨论如何去实现SaaS设计

    Q1:Oauth2为什么不是身份认证协议?

    在很多次的讨论中,我们会发现我们总是会认为Oauth2需要去实现用户的身份认证,因为获取授权的第一步,也是最重要的一步就是获取身份认证,这样我们才能判断是否要颁发access_token给请求方。既然说到了Oauth2不是一个身份认证协议,那么身份认证协议是什么呢?

    Answer1:OIDC

    OIDC是OpenID Connect的简称,OIDC=(Identity, Authentication) + OAuth 2.0(身份认证+授权协议)

    身份认证协议OIDC,他是Oauth2.0的加工版,如同我们将面粉加工成了面包

    在OIDC中会出现3个最为重要的角色:

    • EU(End User):代表最终用户
    • RP(Relying Party):第三方软件,即为认证服务的依赖方
    • OP(OpenID Provider):代表提供身份认证服务方。

    image-20200819160625871

    上图即为OIDC中的三角色与Oauth2.0中的四个角色的关系

    实现OIDC与实现Oauth2.0的区别是什么?

    根据目前的Oauth2实现方案,我们在用户登录授权后会返回4个值:

    • access_token
    • refresh_token
    • exp_time
    • refreshExp_time

    而基于OIDC的实现则会在返回第5个非常重要的信息:id_token

    可能谈到这里大家会比较恼火,为何又多了一个从来没有听说过的东西?微信开放平台不也没有id_token这种东西吗?

    基于我个人的认知,我个人认为微信开放平台是目前最为标准的Oauth2.0协议开放平台了,其实微信一直都返回了id_token

    相信大家可以看到在微信的获取access_token返回接口中返回了一个参数:openId

    其实openId的本质就是id_token,只是id_token的本质就是一个携带用户信息的jwtToken,微信在这里不过是返回形式直接返回了用户id,而非一个token的形式,毕竟如果是jwtToken我们又不得不去考虑它的自验证性的问题了,既然都是要暴露给第三方应用供其解析的,那openId自然可以明文返回

    聊到这里,大家应该明白了,我们目前的access_token中,携带了类似于openId的用户唯一标识,也就是说我们将access_token的功能放大了,他变成了一个身份认证授权通过token,而不是一个单纯的授权token了

    说的最为简单直白:

    id_token的作用代表了它是哪个用户

    access_token的作用在于可不可以访问

    那么我们需要回到话题了,我们做错了吗?是否access_token就一定不能携带用户信息呢?

    其实不然,在健身领域,关于健身补剂一直有一句俚语,抛开剂量谈毒性就是耍流氓

    在我们的身份认证服务中,我个人认为也有相应的一句俚语,抛开环境谈实现也是耍流氓,必须要考量的环境问题一定是

    大家之间是否可信?大家是否同属局域网还是互联网中?

    接下来将要来今天要讨论的第二个话题了

    Q2:如何选择适合我们家的实现

    首先一定会选择微服务架构,在此基础上我们将会考量如何实现OIDC,接下来的问题,参考文档皆为微服务架构设计-Oauth2&JWT

    这个架构的一个比较友好的地方在于,架构思路是基于k8s实现

    名词解析:

    End-User: 最终用户

    Internet:互联网

    FW:防火墙

    Nginx:Nginx反向代理

    Web:部署服务器

    Gateway:网关

    IDP:Identity Provider 认证服务提供者,实现框架:Spring-security可以考量

    BFF: Backend for Frontend 后端For前端微服务层

    DomainSvc:整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务

    DB:数据库

    根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用 BFF 服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。

    由此我们可知晓,无论是BFF还是DomainSvc都不会去存储用户的信息,因为他们都是通过JWT token中包含的用户信息来获取,而JWT token中的用户信息来源是在于IDP服务所对应的LoginSvc Customer DB中

    接下来我们将会根据各个实际情况作为考量点

    Answer2-1:第一方应用 + 资源拥有者凭据模式

    第一方应用,即为完全由我们开发的且处于同一局域网下的完全可信的应用

    因为完全可信,我们可以选择OAuth 2.0 的资源拥有者凭据许可(Resource Owner Password Credentials Grant)

    相信大家看到这里一定会有了争论,这就是我们之前讨论的核心问题了,到底要不要将用户表集成进IDP中,但是我们当时都会产生一个额外的争论,就是Oauth只是一个授权服务。是的如果只看Oauth,我们永远只能得到这个结论,Oauth只是一个授权服务,可我认为我们的问题是否应该考虑,我们要做的是Oauth2.0还是OIDC呢?如果是OIDC,这一切是否就是即符合微服务架构思想,又符合OIDC协议,包含了Oauth2.0协议呢?

    Answer2-2:第三方应用+授权码模式

    关于第三方应用的定义,我们如何来界定一个第三方应用,我认为这一点是相当有必要确认好的

    需要判断第三方应用是否由我们开发,如果不为我们开发,则肯定是第三方应用

    如果由我们开发,处于同一局域网,是否是第三方应用?不处于局域网只能通过互联网交互,是否是第三方应用?

    从上述图示,我们可以得知,用户必须要在守望有用户信息才可以通过授权码模式得到对应的授权码,从而获取access_token

    以上就是关于Oauth2.0的第三方应用+授权码模式的获取Token流程

    综上所述,个人认为关于access_token中是否可以携带用户信息,应该取决于是否第一方应用,如果是第一方应用,对于受保护的资源而言是相当可信的,那么我们可以将用户信息密文存入token中,如果是第三方应用,认为应该将access_token与用户openId分开返回

    之后关于Oauth2.0的安全问题与防止攻击,将会在之后的文档中更新

    参考文档:

    PKCE代码交换证明密钥协议

    OIDC解析

    微服务架构设计-Oauth2&JWT

    极客时间-Oauth2.0实战课

    Oauth2.0 vs Oauth1.0
    github地址

    ]]>
    OAM 创始团队:揭秘 OAM Kubernetes 实现核心原理-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png

    今年 5 月,阿里云和微软云共同宣布,Open Application Model (OAM) 社区携手知名混合云管理项目 Crossplane 社区,联合发布了 OAM 在 Kubernetes 平台上的标准实现与核心依赖库。本次合作达成后,OAM 社区成功的将标准应用定义和标准化的云服务管理能力统一起来,迈出了实现真正意义上的无差别云端应用交付的关键一步 。

    去年 10 月 ,阿里云和微软共同推出了 OAM 项目,旨在构建围绕 Kubernetes 的云原生应用规范。OAM 描述了一个模型 —— 开发人员可以在其中定义应用程序组件;应用程序操作员负责创建这些组件的实例并为它们分配应用程序配置;基础架构运营商负责定义、安装和维护平台上可用的基础服务。

    本次合作是阿里云、微软与 Crossplane 社区的三方技术合作,主要围绕 OAM 在 Kubernetes 上的标准实现以及 Crossplane 项目的 OAM 化展开。因为 Kubernetes 社区在落地 OAM 模型的过程中,提出了关于 OAM 标准实现的诉求。所以这次合作的一个重点,就是三方工程师使用 Go 语言开发了一个 OAM Kubernetes 核心依赖库。这个项目的名字叫做 oam-kubernetes-runtime。OAM Kubernetes Runtime 将会成为 OAM 社区官方维护的基础组件,目标是在 Kubernetes 上提供稳定且统一的 OAM 核心插件。

    为进一步了解本次合作的细节以及 OAM 项目的现状,阿里云高级技术专家 Andy Shi 以及阿里云技术专家孙健波(花名:天元)接受开源中国的专访,共同探讨了 OAM 项目存在的意义。

    详情戳:https://www.oschina.net/question/4487475_2317219

    OAM 因何而生

    我们知道,应用容器技术自诞生开始,就以 “彻底改变了软件打包与分发方式” 的魅力迅速征服了几乎所有的云厂商与数据中心。不过,软件打包与分发方式的革新,并没有能够让软件本身的定义与描述发生本质的变化,基于 K8s 的应用管理体验,也没有让业务研发与运维的工作变得更简单。

    实际上,Kubernetes 带来的云原生技术革命,在于实现了基础设施层的标准化和抽象,但这一层抽象距离业务研发与运维还是太过遥远了。一个最典型的例子,直到今天,Kubernetes 里面始终都没有 “应用” 这个概念,它提供的是更细粒度的 “工作负载” 原语,比如 Deployment 或者 DaemonSet。

    而在实际环境中,一个应用往往是由一系列独立组件的组合,比如一个 “PHP 应用容器” 和一个 “数据库实例” 组成的电商网站;一个 “参数服务节点” 和一个 “工作节点” 组成的机器学习训练任务;一个由 “Deployment + StatefulSet + HPA + Service + Ingress” 组成的微服务应用。

    “应用” 这个概念在 Kubernetes 项目中的缺失,既是一个有意而为之的设计,却也造成了今天云原生应用管理生态的极度碎片化和极高的学习门槛。如何通过标准化的方式去解决这个 “Kubernetes 里到底什么是应用” 的问题,正是 OAM 项目发布的最初始动机。

    有什么意义?

    在 OAM 发布之前,云原生生态里其实并没有一个叫做 “应用” 的概念。哪怕在今天,全世界几乎每一个在落地云原生的团队,都有一个自己定义的 “应用” 的概念,它们的抽象程度层次不齐,定义方式也丰富多样,这就导致了所有围绕着这些 “应用” 构建出来的系统,就成为了一个又一个的大烟囱。

    对于整个云原生生态来说,这种应用层的碎片化和烟囱化,其实对于整个生态演进是非常不利的。而今天的现状也已经证明了这一点,在 Kubernetes 逐渐标准化了基础设施能力的接入方式之后,原本更加接近用户、更加重要的应用管理层,却几乎停滞了演进,在最近几年里没有提出任何一个创新性的思想出来。

    应用管理层停滞不前的结果,就是全世界的业务研发和运维一夜之间都被迫变成了 “容器专家”,一边学习着根本不应该是他们关心的各种 “基础设施即数据(Infrastructure as Data)” 领域的概念(比如:声明式 API,控制器等),一边吐槽 Kubernetes 实在是太复杂了、设计太奇葩了。

    简而言之,Kubernetes 作为一个面向基础设施工程师的系统级项目,主要负责提供松耦合的基础设施语义,这就使得用户学习和操作 Kubernetes YAML 文件的时候,往往会感觉这些文件里的关注点非常底层,学习门槛很高。

    实际上,对于Kubernetes 真正的最终用户比如业务研发人员和运维人员来说,他们并不想配置这些如此底层的资源信息,而是希望有更高维度的抽象。这就要求一个真正面向最终用户侧的应用定义,需要能够为业务研发和应用运维人员提供各自视角的应用定义原语。所以说,OAM 带来的第一个改变,就是提供了一种大家都可以遵循的、标准化的方式来定义更高层级的应用层抽象,并且把“关注点分离”作为这个定义模型的核心思想。

    而 OAM 带来的第二个变化,则是为 Kubernetes 项目带来了应用定义,更确切地说,是对应用本身和它所需运维能力进行定义与描述的标准开源规范。站在 Kubernetes 项目的角度来讲,OAM 是一个 Kubernetes 原生的标准的“应用定义”项目,同时也是一个专注于封装、组织和管理 Kubernetes 中各种“运维能力”、以及连接“运维能力”与“应用”的平台层框架。

    详细的说,OAM 基于 Kubernetes API 资源模型(Kubernetes Resource Model)来标准化应用定义的规范,它强调一个现代应用是多个组件的集合,而非一个简单的工作负载或者 K8s Operator。所以在 OAM 的语境中,一个 PHP 容器和它所依赖的数据库,以及它所需要使用的各种云服务,都是一个“电商网站”应用的组成部分。更进一步的,OAM 把这个应用所需的“运维策略”也认为是一个应用的一部分,比如这个 PHP 容器所需的 HPA(水平自动扩展策略):

    image.png

    以 Crossplane 项目为例,它在本次合作中通过 OAM 升级之后得到了怎样的变化呢?

    “ 作为混合云管理领域中的佼佼者,Crossplane 的 OAM 化保证了今天任何一个符合 OAM 规范的待运行程序、运维能力和它所依赖的云服务,可以组成一个整体在混合云环境中无缝漂移。”

    这种平台无关的应用定义范式,使得应用研发人员只需要通过 OAM 规范来描述他们的应用程序,那么该应用程序就可以在任何 Kubernetes 群集或者 Serverless 应用平台甚至边缘环境上运行,而无需对应用描述做任何修改。本次合作中 Crossplane OAM 版的发布,则意味着 OAM 社区正在将标准应用定义和标准化的云服务管理能力统一起来,从而实现真正的 “云端应用交付” 。

    OAM 如何发挥作用?

    那么 OAM 在一个项目中是如何运作的呢?

    据介绍,OAM 以原生插件的方式运行在 Kubernetes 当中。OAM 强调整个模型是关注点分离的。即业务研发人员负责定义和维护组件 (Component) 来描述服务单元,而运维人员定义运维特征 (Trait),并将其附加到前面的组件上,最后构成 OAM 可交付物 ——ApplicationConfiguration。

    image.png

    这种设计是 OAM 在能够无限接入 Kubernetes 各种能力的同时,保证给业务研发与运维人员提供最佳的使用体验和最低的心智负担的重要基础。与此同时,基础设施工程师可以随时在 Kubernetes 中添加更多工作负载(例如 FaaS)以运行无服务器功能,或者添加运维特性(例如 CronHPA)来定义 CronJob 类型的 HPA 策略。OAM 以标准的声明方式在整个平台中管理应用交付能力和流程,并且提供面向各个角色的 API 原语来表达各自的诉求,最后通过 Kubernetes 把这些诉求落实。

    什么样的项目需要 OAM?

    实际上,几乎所有基于 Kubernetes 的应用管理平台都对通过 OAM 来以标准化的方式去构建自己的应用模型有明确的诉求。另一方面,由于 OAM 是原生的 Kubernetes API 资源模型,这里的迁移过程难度很低,可以通过 API 对象灰度纳管的方式逐步完成迁移操作(通过 OAM 对象逐步接管现有 Kubernetes 对象)。

    而相比于传统 PaaS 封闭的、不能同 “以 Operator 为基础的云原生生态” 衔接的现状,基于 OAM 和 Kubernetes 构建的现代云原生应用管理平台,本质上是一个 “以应用为中心” 的 Kubernetes ,保证了这个应用平台在能够无缝接入整个云原生生态。同时,OAM 可以进一步屏蔽掉容器基础设施的复杂性和差异性,为平台的使用者带来低心智负担的、标准化的、一致的应用管理与交付体验。这就使得一个基于OAM 构建的 Kubernetes 应用平台,首先能够隐藏底层基础设施的细节(例如,是云还是物联网),专注于应用层抽象,提供以应用为中心的资源模型。

    其次,OAM 划分了应用交付路径上的开发、运维、基础架构三种角色,分离了关注点,让流程更加清晰和易于管理。

    第三,OAM 站在 K8s API 资源模型的肩膀之上,提供了可移植的应用与基础设施抽象,让一个应用描述可以完全不加修改的云、边、端等任何环境下直接交付运行起来。

    image.png

    除此之外,OAM 还定义了一组核心工作负载/运维特征/应用范畴,作为应用程序交付平台的基石。而平台开发者也可以添加更多工作负载(例如 FaaS 或者任意云服务),或者添加运维特性(例如 CronHPA)来定义 CronJob 类型的 HPA 策略。OAM 以标准的声明方式在整个平台中管理应用交付能力和流程。当模块化的 Workload 和 Trait 越来越多,就会形成组件市场。而 OAM 就像是这个组件市场的管理者,处理组件之间的关系,把许多组件集成起来变成一个产品交付给用户。OAM 加持下的 Kubernetes 应用管理平台,可以像乐高积木一样灵活组装底层能力、运维特征、以及开发组件。使得应用管理变得统一,功能却更加强大。

    OAM 社区现状

    谈到 OAM 项目社区的现状。“ 作为一个没有同商业诉求绑定的中立开源社区,OAM 生态自成立以来保持着较高的活跃度和参与度,大量的社区 Issue/PR贡献都来自阿里和微软之外的团队比如 AWS、腾讯、字节跳动、谐云、青云、好雨云、第四范式等生态参与者。除了阿里和微软本身以及基于 OAM 实现了内部应用管理架构的统一和标准化之外,不少基于 OAM 的云服务比如阿里云 EDAS 也已经上线。”

    与此同时,OAM 技术体系也开始在很多大型社区用户(比如 MasterCard 万事达卡)中落地,同时也出现了产品和商业化的实践(比如:谐云的可视化OAM实现),甚至来自其它云厂商比如 AWS 的开源项目整合与对接。可以看到,OAM 社区正在迅速成长和壮大中。

    开源社区的运作模式一直是我们比较好奇的地方。据介绍,OAM 项目目前完全由社区驱动,由各子项目的 Maintainer 小组进行维护和管理。社区有每两周一次的社区会议(美国和北京时间各一个)来进行重大事项的讨论与决策和同步项目进度。整个社区的的工作流程按照 Maintainer 席位的投票机制来运转,同时兼顾最终用户的投票权。目前 OAM 社区的核心 Maintainer 来自阿里云,微软和 Crossplane 项目原有的成员。在推广策略上,由多个国际化大厂团队维护的 OAM 项目从诞生起就是完全面向国际化开源社区的运作方式,凭借阿里与微软自身场景,以及整个云原生社区和贡献者的高质量输入来驱动整个项目向正确的方向持续演进,在沟通、分享、协作的氛围中鼓励贡献和发展社区。这种模式下,一旦突破早期破冰阶段,在随后社区传播和推广方面会带来病毒式的效果。

    目前 OAM 的版本是 v1alpha2 ,OAM 的版本之后会不停迭代,根据实际的场景持续演进;当然,同时 spec 本身也会保证规范的稳定和兼容。这个标准的更新速度主要是取决于用户的接受程度和反馈的情况,并且会在今年发布 Beta 版。本次合作中,OAM 已经发布了 Kubernetes 的标准实现与核心依赖库,这也就意味着未来整个开源生态都可以直接通过对接 Crossplane 或者 oam-kubernetes-runtime 来支持 OAM 标准,所以这样的项目很快会越来越多。

    本文转自<阿里巴巴云原生技术圈>——阿里巴巴云原生小助手

    ]]>
    案例速览 | 如何为3~12岁孩子提供全方面监控能力?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 17.jpg

    叫叫阅读系列是成都书声科技有限公司(铁皮人)旗下的教育 Apps 。主要针对 3-12 岁孩子,以儿童身心发展规律为依据,秉承叶圣陶先生的语文教育论,多读书,读好书,勤思考。由小学语文老师、幼小衔接专家、诵读老师、儿童节目主持人组成的优秀师资团队带领孩子解读名篇,领略汉语文字之美,建立其知识格局,完善其独立人格,提升其语文素养。通过“在线互动课堂+在线班主任辅导+线下配纸质书”的三位一体的教学模式,丰富孩子的知识,培孩子的能力,提升孩子的素养。

    2007 到 2011 年,叫叫阅读创作的绘本 500 余册,部分绘本被大中华,北美等地区选为课外指定阅读绘本;2011 到 2017 年,原创的教育类 App 以及叫叫系列产品诞生,斩获用户 7000 万;近年来,通过提供更为丰富的在线语文,数学课程,特别是疫情下,学员人数累创新高。

    新的需求

    随着在线教育的风口来临,叫叫阅读的业务呈现指数般增长,依托于以微服务化为基础的互联网架构下,系统中拆分的应用越来越多,应用间以及应用内的问题监控/定位/分析越发困难,急切需要一个如下功能的 APM 工具来解锁提升技术团队的相关能力:

    全方位的应用监控能力:不但可以监控 CPU /内存、网络、IO、磁盘等基础设施;还可以监控 JVM 、线程、异常、慢 SQL 等情况;最重要还可提供基于应用/接口的各种 metrics , 业务调用链的能力,最好能方便地获取 SQL 的绑定参数。

    无侵入的应用接入方式:不需要开发人员主动埋点,无需要业务方引入 jar 依赖,可在容器/ECS 等多种环境上部署便拥用全方位的应用监控能力。

    低损耗的资源占用比例:接入的 APM 工具对业务应用本身的资源占用比例绝大部分时间不超过 5% , APM 本身对宿主应用的资源占用的有保护措施。

    白屏化的配置使用能力:可以通过白屏化进行采样率 / SQL 绑定参数的提取等等的调整,也可以通过批量标签化管理应用的监控接入启停,提供丰富的告警配置能力。

    解决方案:

    通过阿里自研的 ARMS 应用实时监控工具,既满足无侵入的接入方式和低损耗的资源占用比例,还提供了全方位的应用监控能力和白屏化的配置使用能力,而且 ARMS 结合众多客户场景和专家经验,提供智能诊断功能。
    image.png

    效果:

    1、平稳支撑在线教育的增长风口:近来在线教育行业正在风口上高速增长,叫叫阅读在ARMS的帮助下,及早发现与修复问题,帮助业务系统渡过了一浪赛过一浪的学员使用峰值,为业务的增长提供了有力的保障。

    2、解锁技术团队的定位技能包:有了ARMS的支持,不但资深工程师掌握了快速定位分析问题的能力,刚上手的同学也能在短期内就拥有相同的技能包。在ARMS的长期使用熏陶中,整个技术团队的编码能力潜移默化地得到了升华,获得了生产力的提升。

    客户证言:

    “阿里云 ARMS 监控定位分析告警的能力超出了我们的预期,在接入与管理上非常灵活简洁!借助 ARMS 的能力,完全解锁了我们技术团队的系统定位分析技能包,对系统的稳定与高效运行有了质的飞跃,我们的业务部门对系统的提升也称赞不绝。”

    加入阿里云在线教育客户交流钉钉群

    欢迎扫码加入在线教育行业客户交流钉钉群,阿里巴巴众多专家将在群内定期分享行业最佳实践和前沿技术干货,扫码入群,与更多行业精英互动交流。钉钉扫码或搜索钉钉群号均可加入:35712134。
    image.png
    【填问卷抽淘公仔-阿里云中间件用户调研】

    点击链接,一分钟填问卷抽淘公仔:
    https://survey.aliyun.com/apps/zhiliao/YmW95Gk8bU

    ]]>
    云端IDE:阿里云机器学习与PAI-DSW | 《阿里云机器学习PAI-DSW入门指南》-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 经过20年的快速发展,阿里经济体已经组建了一个庞大的商业生态圈,并在支付、云计算、本地生活服务等行业保持互联网巨头地位。2020财年交易额突破1万亿美元,全球第一家;阿里云支撑了2019年双11 期间峰值 54.4 万笔/秒、单日数据处理量达到 970PB 的世界级的流量洪峰,成为业界第一个实现此壮举的云计算公司。

    阿里云机器学习平台正是伴随着这样庞大而复杂的阿里经济体业务成长起来的。下面我们将带着大家掀开阿里云机器学习技术大图的一角,看看阿里云机器学习,特别是机器学习工程上的发展、沉淀和创新。

    阿里云机器学习技术大图

    我们从用户和技术的两个角度来梳理阿里云机器学习的技术体系大图。从用户的角度来说,根据使用机器学习的深度不同,在云栖大会上,我们展示了飞天AI平台的技术分层关系:

    image.png(注:不是一个完整的产品列表,而是一些核心的样例)

    从技术的角度说,机器学习从算法到底层的硬件,都涉及到不同的技术方向。下面是我们对于核心技术能力上的一个总体描述:
    image.png
    每个技术方向上都形成各自的布局和沉淀,接下来我们会重点讲述作为机器学习重要组成部分的工程能力体系建设。

    阿里云机器学习工程能力体系

    阿里云在机器学习工程体系建设上,也经历了各领域业务需求驱动和技术驱动分阶段螺旋式递进上升的过程。由最初的通过传统机器学习算法进行数据价值的粗加工,到今天以深度学习为主、支撑各类“行业大脑”解决方案的人工智能工程体系。

    阿里云的机器学习工程能力体系建设始终围绕着更高效的融合人工智能三要素(算法、数据、算力)进行展开,即追求不断提升整个工程体系中的计算效率、数据效率以及工程效率,从而能够更好的支撑阿里经济体各方面业务快速发展的需求,并通过阿里云对外进行技术输出,推动人工智能领域的技术变革,产生更大的社会效益,实现普惠人工智能。

    经过多年的发展创新,阿里云在AI托管平台技术层进行了系统性的建设,极大提升了算法研发、共享、部署、输出的效率,在此基础上沉淀出多个具有用户粘性和场景差异化的开发平台,这里我们选取阿里云机器学习PAI(Platform of Artificial Intelligence)作为代表来着重来介绍。

    PAI是一款覆盖机器学习全流程的一站式机器学习平台产品,集数据预处理、特征工程、自动调参、模型训练、在线预测为一体,为用户提供低门槛、高性能的云端机器学习服务。

    PAI相关技术脱胎于阿里集团内数十个BU的上千个业务体系,沉淀了大量的覆盖各个领域的优质分布式算法、框架、平台等,同时也在不断完善和扩充机器学习生态。
    image.png

    阿里云机器学习PAI-DSW

    作为在AI战线上辛勤耕耘的算法工作者,你是否也常常遇到下面的情形:

    算法需要运行在GPU上,可是长时间申请不到GPU机器,只能干着急。

    终于GPU机器申请到了,却不能马上开始使用,需要先安装GPU驱动和各种依赖等等,感觉有些浪费时间。

    好不容易机器环境弄好了,可当某天更新算法代码后变得很慢, 排查半天才发现是GPU驱动需要升级补丁,很是无奈。

    生产环境机器网络隔离,在线上要debug代码,只能使用GDB在命令行进行,开发效率大大降低。

    在本地采用PyCharm这样的IDE开发好代码,而数据在生产环境,不允许下载,只能把代码拷贝到线上机器运行,发现问题后,又得回到本地修改调试后再来一遍,非常不便。

    PAI Studio采用图形化拖拽式,像搭积木一样分分钟就构建一个完整的工作流,很炫酷。 但想要定制发布自己的组件时,却不知从何下手。

    在长期与算法工程师同学沟通合作的过程中,我们发现了算法工程师面临的这些问题。提升机器学习工程效率,降低人工智能使用门槛,急需一个简单、轻量、好用的工具平台,从而让算法工程师更加专注于模型设计本身。PAI DSW(Data Science Workshop)就是PAI团队为解决算法工程师的以上痛点,新推出的一款云端机器学习开发IDE。

    PAI-DSW集成了Jupyterlab、WebIDE等多种开源项目,在阿里巴巴集团内上百个BU和上千名工程师的打磨之下性能和功能上都进行了一定的调优。数据上打通了ODPS等多个数据源,方便用户在构建模型的时候免去重新构建数据管道的工作。同时,在深度学习上,PAI-DSW内置了Tensorboard,可以通过简单的拖拽的方式来帮助深度学习的开发者更好的完成深度学习场景下神经网络的建模。下图展示了DSW在机器学习平台PAI产品架构中的位置:

    image.png
    (DSW在机器学习平台PAI产品架构中的位置)

    简单来说,PAI-DSW可以实现多实例、多环境,GPU/CPU资源、JupyterLab、WebIDE以及全屏使用Terminal无干扰工作。目前PAI-DSW已经向所有阿里云的用户免费开放了探索者版,只需要登陆阿里云然后打开 https://dsw-dev.data.aliyun.com/#/ 即可即刻开始云上数据科学之旅。本书后面两个章节将详细介绍如何使用PAI-DSW这一简单好用的工具。

    ]]>
    阿里云云原生数据湖分析DLA Serverless Spark重磅发布,助力企业低成本挖掘OSS数据价值-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、背景概述

    1.1 什么样的客户需要数据湖

    在数据处理领域,数据湖相对来说是一个比较新的概念,它的提出可以很好地帮助企业应对当前数据场景越来越多、数据结构越来越复杂、数据处理的需求越来越多样化的问题。传统的单机数据库技术倾向于大一统,一个数据库可以解决数据存储、在线交易、在线分析、离线报表等功能,好处是简单,数据只有一份,缺点是各个功能都做了取舍,很难解决规模的问题。为了突破数据规模的瓶颈,大数据技术更倾向于针对单独领域做深度定制,比如海量文件存储使用HDFS、海量对象存储使用OSS/S3、宽表存储使用BigTable/HBase、嵌套数据使用MongoDB、大规模TP数据使用PolarDB、大规模AP数据使用ADB/Clickhouse、日志数据使用LogService等等。

    在很多企业里面,不同的部门业务不同,采用的数据方案也不同。在企业发展的前期,更多是靠业务模式驱动、流量驱动,数据复杂度的问题还不明显,后期则需要精细化运营、向数据要红利,数据管理的难度就成为企业的痛点。数据湖的出现可以很好地解决这个痛点,这也是为什么各个云厂商都推出了数据湖产品,数据湖产品和解决方案越来越得到客户的认可。Gartner 2020年发布的报告显示目前已经有39%的用户在使用数据湖,34%的用户考虑在1年内使用数据湖。

    1.2 Aliyun DLA数据湖整体方案

    image.png

    Aliyun数据湖分析(DLA)产品提供了数据湖的一站式解决方案。OSS对象存储采用KV的技术架构,可以实现无限扩展,是公认的数据湖存储底座。用户可以通过离线ETL和在线增量ETL将在线数据和实时增量数据,同步到OSS中,然后对数据做深度的计算和分析。用户也可以直接访问这些在线库,做在线的联邦分析。为了方便用户管理数据湖中的数据,我们提供了统一的数据湖管理方案。数据湖管理可以统一存储数据湖中数据的元信息给计算引擎使用,另外还提供元数据动态爬取功能,可以动态解析OSS数据目录结构和数据格式,省去了用户手动创建表和分区的工作。DLA同时提供了SQL和Spark两个引擎,SQL基于Presto实现,可以实现在线分析,Spark可以实现用户自定义代码和复杂计算逻辑。同时,DLA跟DMS和QuickBI进行了深度集成,方便用户实现更丰富的开发和管理逻辑。

    二、DLA Serverless Spark架构解析

    2.1 DLA Spark为什么是云原生 + Serverless

    2.1.1 数据湖天然存储计算分离

    数据湖技术处理的对象是用户已存在的所有的数据。这里提到两个关键定语“已存在的”和“所有的”。要处理好“已存在的”原始数据,最好的方式显然不是先将其导入到数据仓库中,最好是能够原地分析,使用单独的计算资源来计算分析各种类型存储系统中的数据,这就是存储计算分离的架构。存储计算分离的架构能够做到存储和计算的分层弹性,存储可以无限扩展,计算能够按需弹性调度。这里提到了弹性计算,计算的弹性可以分很多层次:

    • 集群级弹性:用户可以按需购买ECS并部署集群,然后后再执行计算任务,任务执行完之后再释放掉集群;
    • 作业级弹性:用户每个作业都是单独的计算资源,作业执行完之后立即释放;
    • 作业内弹性:一个任务在执行过程中,不同的阶段所需要消耗的资源不同,典型地,分布式作业出现长尾时,大部分资源都是空闲的,此时可以将空闲资源释放掉;
    • 容器内弹性:计算任务的进程在执行过程中不同时段所需要的资源不同,是否可以做到动态调节;

    弹性的粒度越细,资源的利用率越高,成本也就越低。相对于传统IDC,云的最大的优势之一就是弹性,数据湖天然是存储计算分离的,跟云弹性的能力十分匹配,数据湖场景下,云原生 + 弹性是必然趋势。

    2.1.2 Serverless是云的趋势

    Serverless是业内公认的云发展的一个趋势。其实不难理解,从IDC自建到搬站上云再到Serverless化符合技术和客户需求发展的规律。

    在云最初产生的时候,大家把云计算基本等同于虚拟化技术,客户最普遍的需求是将线下的站点搬上云,用户把云当做对线下硬件的替换,企业的IT研发、运维大体跟上云前一致。用户对大数据系统,比如Hadoop集群,也类似,把线下集群换成虚拟集群,再由专门团队运维大数据集群。在云市场发展的第一阶段,这种模式有用户习惯的因素,也有性能、成本等的因素,自建跟云产品在性能上差距不太大,在成本上甚至更便宜,客户也拥有一定的掌控感。然而,云的技术会继续往精细化深入发展,从资源共享的角度来看,每套Hadoop集群都有一套自己的管控,这些管控包括Master节点,Core节点上面的守护进程,以及配套的监控、运维管理等系统。这些资源和人力投入实际上对企业来说都只是成本,并不产生真正的计算价值。

    随着云技术的发展,Serverless形态可以很好的解决企业在第一阶段遇到的问题,企业只需要关心自己的业务开发,只为真正参与计算的资源付费,云产品把管控的部分统一管理起来,边际成本可以做到很低。另外,云产品会深度扎根于云基础设施,对性能、弹性、使用体验等做持续深入优化,相对于第一阶段的用户自建模式可以实现数倍的性价比提升。最后,随着云产业的发展,云产品会越来越标准化,客户也不必担心被云绑定的问题。在云市场进入第二阶段,Serverless形态的产品让企业更专注于自身业务,进一步降低运维和资源成本,让开发者拥有更好的使用体验以及更低的入门门槛。

    2.1.3 Spark一站式解决数据湖计算需求

    “所有的”意味着数据的来源途径多种多样,存储位置多种多样,数据格式多种多样。这就要求计算引擎可以支持所有数据类型,并且可以很方便的做分析、计算,因为数据是“已存在的”,Schema只能在读取的时候才能确定,而不能依赖用户把表都提前建好。Spark非常适合数据湖场景:

    • Spark本身内置了十分丰富的数据源连接器,接口也很方便扩展;
    • Spark既支持使用SQL,又支持编写多种语言的DataFrame代码,兼具易用性和灵活性;
    • Spark一站式引擎能力,同一个引擎同时提供SQL、流、机器学习、图计算的能力;

    DLA团队将Serverless、云原生、Spark技术优势深度整合到一起,提供Serverless Spark产品,兼具三者优势。下面我们将对Serverless Spark产品架构进一步解析。

    2.2 自建Spark集群 VS Serverless Spark

    image.png

    上图中左半部分是传统最常见的集群版Spark,右边是Serverless Spark。集群版Spark大家都比较熟悉,每个用户一个集群,集群内部拥有一套完整的Spark管控,用户使用方式上跟传统IDC模式一致。

    Serverless Spark将管控完全多租户化,相对于传统的集群模式,Serverless Spark抽象出一个虚拟集群的概念。虚拟集群只是承载用户对作业进行控制的一些配置,包括网络的配置、安全隔离的配置、计算资源Quota的配置、作业通用参数的配置等。由于是虚拟集群,所以集群创建基本都可以做到秒级完成。用户创建完虚拟集群之后,就可以往虚拟集群提交作业,去访问用户在各个存储引擎中的数据。

    Serverless Spark在弹性能力上基于Aliyun Kubernetes云原生技术深度定制,调度层可以实现秒级拉起,一分钟可以并行拉起300个计算节点,后续还会持续优化。Serverless Spark控制服务实现了对云资源、作业、库表列元数据、租户、安全等管理,对上提供阿里云OpenAPI,用户可以通过OpenAPI实现作业的提交和管理。另外Serverless Spark会跟其他云产品深度集成,进一步丰富用户的使用场景,目前Serverless Spark已经跟DMS进行集成,可以实现工作流,定时调度等管理。

    对比维度 自建Hadoop集群 Serverless版
    内置存储 HDFS 数据湖存储OSS
    集群形态 ECS集群,需配置Master、Core规格 虚拟集群,只需配置资源quota
    集群管理 扩缩容、升降配 quota调整,参数配置
    售卖形态 实例型,集群粒度,按ECS规格售卖 服务型,按作业实际使用CU*时收费
    资源弹性 集群级别 作业级弹性
    性价比 低:管控开销+资源空闲开销 高:按需付费
    维护成本 高:运维团队维护集群 低:管理员分配资源
    作业管理 Livy、脚本、控制台、定时调度、作业编排 OpenAPI、脚本、控制台、定时调度、作业编排
    租户支持 依赖开源,配置复杂 Aliyun RAM
    学习成本 高:需要先学习集群管理与配置 低:只需要学习Spark开发

    在上表中,我们在集群运维、性价比、作业开发等方面对比了两种形态的差异。从使用体验上,Serverless Spark可以做到开箱即用,一分钟就可以跑通一个Spark作业;从成本上,由于将管控完全多租户化,用户不需要承担这部分额外开销,只需要实际使用付费;从运维方面,一个企业中一个管理员就可以实现整体管理工作,大大降低运维成本。

    三、DLA Serverless Spark性价比

    3.1 1TB Terasort DLA Spark vs 自建Hadoop 性价比对比

    image.png

    我们先对比下1TB数据情况下,数据湖方案跟传统Spark集群方式的性价比。相关配置说明如下:

    • TeraSort输入1TB + shuffle大约1TB + 输出1TB,作业每天跑一次。
    • Hadoop集群配置:单Master(4c8g) + 5个Core(8c32g),Core节点配置4块500GB的高效云盘。一般高效云盘采用2备份的HDFS配置。这里存储空间是4 500GB 5 / 2 = 5TB。
    • Serverless Spark采用40CU + 2TB OSS。

    对比结果如右图所示,作业性能上Serverless Spark跟Hadoop基本持平,但是性价比差异非常大,DLA Serverless Spark会节约80%。也就是会有4-5倍的性价比提升。

    需要说明的是:

    • Hadoop集群配置是一个总容量5TB集群的典型配置,一般情况下集群不能把磁盘都用满,要留一定buffer,否则系统可能会出现各种空间不足问题。
    • Serverless Spark完全按需使用存储和计算资源。
    • Serverless Spark对OSS访问实现了深度定制优化,性能相比于社区提升1倍左右。

    3.2 10TB Terasort DLA Spark vs 自建Hadoop 性价比对比

    image.png

    对于更大规模数据,比如10TB,对比结果如上图所示。相关配置说明:

    • TeraSort输入10TB + shuffle大约10TB + 输出10TB。作业每天跑一次。
    • Hadoop集群配置:单Master(4c8g) + 5个Core(16c64g),Core节点配置8 5.5TB的本地盘。一般本地盘采用3备份的HDFS配置。这里存储空间是5 8 * 5.5TB / 3 = 73TB。
    • Serverless Spark采用80CU + 50TB OSS。

    我们发现性能上DLA Spark提升了1倍,成本反而降低了一半,性价比提升4倍。

    需要说明的是:

    • Hadoop集群采用的是大数据量场景下的典型配置,采用本地盘D1机型,成本相对于云盘要便宜。由于本地盘机型要求空间比较大,16c64g的只能配置44TB的本地盘,考虑到Hadoop集群的本地盘通常不能打太满,为了公平起见,我们采用OSS的空间是50TB。
    • 在分析性能时发现,在10TB场景下,本地盘的存储和shuffle之间会有IO带宽上明显的争用,而Serverless Spark计算节点自带essd云盘,将shuffle盘完全独立,对性能提升有较大的贡献。

    3.3 Serverless Spark访问用户自建Hadoop

    image.png

    用户可以将自建Hadoop和Serverless Spark混合使用,用户Hadoop集群在高峰期需要更多的计算资源,用户可以直接提交Serverless Spark来实现对计算弹性的需求。由于Serverless Spark可以做到直接跟用户VPC打通,可以直接使用内网带宽,经过对比两者的性能基本持平。

    四、使用场景

    image.png

    面向数据湖中的各种各样的数据,Spark可以用于如下场景:

    • 生态打通:Spark的多数据源能力,提供外部数据源批量入库、联邦分析能力;DLA Spark会跟云上数据源做深度集成优化,解决稳定性问题并提升性能;
    • 算法及用户可编程:支持python、java、scala、R、SQL多语言,支持复杂的数据过程处理(类似PL/SQL)、机器学习等;
    • 离线数仓(复杂分析):支持复杂离线分析,提供天/月级别的报表等;
    • 半结构化/非结构化处理:搭配HDFS/OSS存储为数据库添加非结构化数据存储处理能管理(CSV、XML、Parquet多种存储);
    • 离线ETL:可以用于各存储引擎之间的数据转换、清洗、归档等操作;
    • 实时计算:Spark Streaming + Hudi可以实现数据实时转存,实现在不影响在线库稳定性情况下,对实时数据的复杂分析;Spark Streaming实时ETL将多个MySQL大表,合并为一个ADB宽表,避免大表在线join,提高在线分析性能。

    image.png

    上图是某游戏公司使用DLA解决数据湖场景问题的方案。用户要对在线库RDS做分析,但又怕影响RDS的稳定性,于是选择采用DLA一键建仓功能将其同步到OSS里面转为列存;用户游戏APP的日志数据会接入到Kafka里面,然后通过Spark Streaming实时写入到OSS,数据采用Hudi增量数据湖格式。数据流入到OSS后,用户会对数据使用DLA SQL(Presto)做在线分析,也会对历史数据做复杂分析和机器学习,挖掘游戏玩家的使用规律。

    五、总结与展望

    针对用户面临的越来越多的数据湖场景典型问题,阿里云DLA产品提供了一体化解决方案,从数据湖管理到数据湖分析和计算。相对于在线引擎,Spark更适合弹性计算架构,可以跟云原生的弹性能力深度整合起来。从传统IDC到搬站上云到完全Serverless化,这条路径已经被越来越被认可为云技术的发展路径。DLA Spark采用完全云原生 + Serverless形式,相对于传统的自建Hadoop在性价比上拥有数倍的优势。

    未来,DLA Serverless Spark会面向数据湖场景做进一步深度优化:

    • 更便宜:实现更细粒度的弹性继续降低成本;
    • 更快:优化Spark内核本身,特别是跟云存储之间做深入定制优化,提升引擎性能;
    • 更好用:降低Spark开发难度,更进一步提升用户使用体验。

    注:DLA Serverless Spark控制台使用链接,DLA Serverless Spark帮助文档

    image.png

    ]]>
    关于阿里云DRDS 5.3升级 详细介绍 性能提升300%-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    MySQL主从复制读写分离,看这篇就够了!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 思维导图

    微信公众号已开启:【java技术爱好者】,还没关注的记得关注哦~

    文章已收录到我的Github精选,欢迎Starhttps://github.com/yehongzhi/learningSummary

    前言

    在很多项目,特别是互联网项目,在使用MySQL时都会采用主从复制、读写分离的架构。

    为什么要采用主从复制读写分离的架构?如何实现?有什么缺点?让我们带着这些问题开始这段学习之旅吧!

    为什么使用主从复制、读写分离

    主从复制、读写分离一般是一起使用的。目的很简单,就是为了提高数据库的并发性能。你想,假设是单机,读写都在一台MySQL上面完成,性能肯定不高。如果有三台MySQL,一台mater只负责写操作,两台salve只负责读操作,性能不就能大大提高了吗?

    所以主从复制、读写分离就是为了数据库能支持更大的并发

    随着业务量的扩展、如果是单机部署的MySQL,会导致I/O频率过高。采用主从复制、读写分离可以提高数据库的可用性

    主从复制的原理

    ①当Master节点进行insert、update、delete操作时,会按顺序写入到binlog中。

    ②salve从库连接master主库,Master有多少个slave就会创建多少个binlog dump线程。

    ③当Master节点的binlog发生变化时,binlog dump 线程会通知所有的salve节点,并将相应的binlog内容推送给slave节点。

    ④I/O线程接收到 binlog 内容后,将内容写入到本地的 relay-log。

    ⑤SQL线程读取I/O线程写入的relay-log,并且根据 relay-log 的内容对从数据库做对应的操作。

    如何实现主从复制

    我这里用三台虚拟机(Linux)演示,IP分别是104(Master),106(Slave),107(Slave)。

    预期的效果是一主二从,如下图所示:

    Master配置

    使用命令行进入mysql:

    mysql -u root -p

    接着输入root用户的密码(密码忘记的话就网上查一下重置密码吧~),然后创建用户:

    //192.168.0.106是slave从机的IP
    GRANT REPLICATION SLAVE ON *.* to 'root'@'192.168.0.106' identified by 'Java@1234';
    //192.168.0.107是slave从机的IP
    GRANT REPLICATION SLAVE ON *.* to 'root'@'192.168.0.107' identified by 'Java@1234';
    //刷新系统权限表的配置
    FLUSH PRIVILEGES;

    创建的这两个用户在配置slave从机时要用到。

    接下来在找到mysql的配置文件/etc/my.cnf,增加以下配置:

    # 开启binlog
    log-bin=mysql-bin
    server-id=104
    # 需要同步的数据库,如果不配置则同步全部数据库
    binlog-do-db=test_db
    # binlog日志保留的天数,清除超过10天的日志
    # 防止日志文件过大,导致磁盘空间不足
    expire-logs-days=10 

    配置完成后,重启mysql:

    service mysql restart

    可以通过命令行show master statusG;查看当前binlog日志的信息(后面有用):

    Slave配置

    Slave配置相对简单一点。从机肯定也是一台MySQL服务器,所以和Master一样,找到/etc/my.cnf配置文件,增加以下配置:

    # 不要和其他mysql服务id重复即可
    server-id=106

    接着使用命令行登录到mysql服务器:

    mysql -u root -p

    然后输入密码登录进去。

    进入到mysql后,再输入以下命令:

    CHANGE MASTER TO 
    MASTER_HOST='192.168.0.104',//主机IP
    MASTER_USER='root',//之前创建的用户账号
    MASTER_PASSWORD='Java@1234',//之前创建的用户密码
    MASTER_LOG_FILE='mysql-bin.000001',//master主机的binlog日志名称
    MASTER_LOG_POS=862,//binlog日志偏移量
    master_port=3306;//端口

    还没完,设置完之后需要启动:

    # 启动slave服务
    start slave;

    启动完之后怎么校验是否启动成功呢?使用以下命令:

    show slave statusG;

    可以看到如下信息(摘取部分关键信息):

    *************************** 1. row ***************************
                   Slave_IO_State: Waiting for master to send event
                      Master_Host: 192.168.0.104
                      Master_User: root
                      Master_Port: 3306
                    Connect_Retry: 60
                  Master_Log_File: mysql-bin.000001
              Read_Master_Log_Pos: 619
                   Relay_Log_File: mysqld-relay-bin.000001
                    Relay_Log_Pos: 782
            Relay_Master_Log_File: mysql-bin.000001 //binlog日志文件名称
                 Slave_IO_Running: Yes //Slave_IO线程、SQL线程都在运行
                Slave_SQL_Running: Yes
                 Master_Server_Id: 104 //master主机的服务id
                      Master_UUID: 0ab6b3a6-e21d-11ea-aaa3-080027f8d623
                 Master_Info_File: /var/lib/mysql/master.info
                        SQL_Delay: 0
              SQL_Remaining_Delay: NULL
          Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
               Master_Retry_Count: 86400
                    Auto_Position: 0

    另一台slave从机配置一样,不再赘述。

    测试主从复制

    在master主机执行sql:

    CREATE TABLE `tb_commodity_info` (
      `id` varchar(32) NOT NULL,
      `commodity_name` varchar(512) DEFAULT NULL COMMENT '商品名称',
      `commodity_price` varchar(36) DEFAULT '0' COMMENT '商品价格',
      `number` int(10) DEFAULT '0' COMMENT '商品数量',
      `description` varchar(2048) DEFAULT '' COMMENT '商品描述',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

    接着我们可以看到两台slave从机同步也创建了商品信息表:
    在这里插入图片描述

    主从复制就完成了!java技术爱好者有点东西哦~

    读写分离

    主从复制完成后,我们还需要实现读写分离,master负责写入数据,两台slave负责读取数据。怎么实现呢?

    实现的方式有很多,以前我公司是采用AOP的方式,通过方法名判断,方法名中有get、select、query开头的则连接slave,其他的则连接master数据库。

    但是通过AOP的方式实现起来代码有点繁琐,有没有什么现成的框架呢,答案是有的。

    Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy两部分组成。

    ShardingSphere-JDBC定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

    读写分离就可以使用ShardingSphere-JDBC实现。
    在这里插入图片描述

    下面演示一下SpringBoot+Mybatis+Mybatis-plus+druid+ShardingSphere-JDBC代码实现。

    项目配置

    版本说明:

    SpringBoot:2.0.1.RELEASE
    druid:1.1.22
    mybatis-spring-boot-starter:1.3.2
    mybatis-plus-boot-starter:3.0.7
    sharding-jdbc-spring-boot-starter:4.1.1

    添加sharding-jdbc的maven配置:

    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>4.1.1</version>
    </dependency>

    然后在application.yml添加配置:

    # 这是使用druid连接池的配置,其他的连接池配置可能有所不同
    spring:
      shardingsphere:
        datasource:
          names: master,slave0,slave1
          master:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.jdbc.Driver
            url: jdbc:mysql://192.168.0.108:3306/test_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
            username: yehongzhi
            password: YHZ@1234
          slave0:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.jdbc.Driver
            url: jdbc:mysql://192.168.0.109:3306/test_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
            username: yehongzhi
            password: YHZ@1234
          slave1:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.jdbc.Driver
            url: jdbc:mysql://192.168.0.110:3306/test_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
            username: yehongzhi
            password: YHZ@1234
        props:
          sql.show: true
        masterslave:
          load-balance-algorithm-type: round_robin
        sharding:
          master-slave-rules:
            master:
              master-data-source-name: master
              slave-data-source-names: slave0,slave1

    sharding.master-slave-rules是标明主库和从库,一定不要写错,否则写入数据到从库,就会导致无法同步。

    load-balance-algorithm-type是路由策略,round_robin表示轮询策略。

    启动项目,可以看到以下信息,代表配置成功:

    编写Controller接口:

        /**
         * 添加商品
         *
         * @param commodityName  商品名称
         * @param commodityPrice 商品价格
         * @param description    商品价格
         * @param number         商品数量
         * @return boolean 是否添加成功
         * @author java技术爱好者
         */
        @PostMapping("/insert")
        public boolean insertCommodityInfo(@RequestParam(name = "commodityName") String commodityName,
                                           @RequestParam(name = "commodityPrice") String commodityPrice,
                                           @RequestParam(name = "description") String description,
                                           @RequestParam(name = "number") Integer number) throws Exception {
            return commodityInfoService.insertCommodityInfo(commodityName, commodityPrice, description, number);
        }

    准备就绪,开始测试!

    测试

    打开POSTMAN,添加商品:

    控制台可以看到如下信息:

    查询数据的话则通过slave进行:

    就是这么简单!

    缺点

    尽管主从复制、读写分离能很大程度保证MySQL服务的高可用和提高整体性能,但是问题也不少:

    • 从机是通过binlog日志从master同步数据的,如果在网络延迟的情况,从机就会出现数据延迟。那么就有可能出现master写入数据后,slave读取数据不一定能马上读出来

    可能有人会问,有没有事务问题呢?

    实际上这个框架已经想到了,我们看回之前的那个截图,有一句话是这样的:

    微信公众号已开启:【java技术爱好者】,没关注的同学记得关注哦~

    我是java技术爱好者,罗定的java精英,人称 【罗ja英】

    坚持原创,持续输出兼具广度和深度的技术文章。

    上面所有例子的代码都上传Github了:

    https://github.com/yehongzhi/mall

    你的点赞是我创作的最大动力~

    拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!
    3.png

    ]]>
    2020阿里云服务器如何购买(图文教程)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    阿里架构师8问Redis,全对算你赢-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1、Redis 官方为什么不提供 Windows 版本?

    因为目前 Linux 版本已经相当稳定,且Linux操作系统自带的epoll相关函数, 在高并发情况下性能一般比windows的select函数性能较好,为了高性能起见, Redis官网不提供windows 版本。

    image.png

    2、使用Redis有什么缺点?

    缓存和数据库双写一致性问题
    缓存雪崩问题
    缓存击穿问题
    缓存的并发竞争问题

    3、Redis是单线程还是多线程?

    回答: 主线程是单线程, 但6.0版本前后线程机制有做调整, 只要看懂下面这张图即可(看不懂没关系, 后面会讲到)
    image.png

    4、MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

    非常简单, 当Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

    5、Redis过期策略和内存淘汰机制?

    正解:Redis采用的是定期删除+惰性删除策略。

    6、Redis 的持久化底层如何实现的?有什么优点缺点?

    RDB: 在不同的时间点将 redis 的数据生成的快照同步到磁盘等介质上):内存到硬盘的快照,定期更新。缺点:耗时,耗性能(fork+io 操作),易丢失数据。
    AOF:将 redis 所执行过的所有指令都记录下来,在下次 redis 重启时,只需要执行指令就可以了):写日志。缺点:体积大,恢复速度慢。
    Redis4.0 之后有了混合持久化的功能,将 bgsave 的全量和 aof 的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。

    7、什么是缓存穿透?如何解决缓存穿透问题?

    指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。
    穿透解决方案如下:
    1.查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短;
    2.布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对 DB 的查询。

    8、Redis 常见的性能问题和解决方案

    这个你能答上来吗?

    Redis也是大厂面试最爱问的,除了上面这些问题,还包括Redis客户端、Redis高级功能、Redis持久化和开发运维常用问题探讨、Redis复制的原理和优化策略、Redis分布式解决方案等。

    ]]>
    oracle故障脚本收集-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1、.万能重启方法
    如应急情况,需要重启数据库:
    tail -100f <对应路径>alert_fgedu.log
    alter system switch logfile;
    alter system checkpoint;
    shutdown immediate;
    如果不能正常关机,可以使用shutdown abort强制关机;
    startup

    2.操作系统性能(通常故障出现时最先检查的内容)
    top、topas、vmstat、iostat、free、nmon

    3.批量杀进程(数据库挂起时应急恢复)
    3.1.kill所有LOCAL=NO进程
    ps -ef|grep LOCAL=NO|grep $ORACLE_SID|grep -v grep|awk '{print $2}' |xargs kill -9
    3.2.按用户批量杀进程
    select 'alter system kill session ''' || s.sid || ',' || s.serial# ||

       '''; -- kill -9  ' || p.spid

    from v$session s, v$process p
    where s.PADDR = p.addr and s.username='&username'

    4.数据库杀会话(应急方法)
    4.1.杀某个SID会话
    SELECT /+ rule / sid, s.serial#, 'kill -9 '||spid, event, blocking_session b_sess
    FROM v$session s, v$process p WHERE sid='&sid' AND s.paddr = p.addr order by 1;
    4.2.根据SQL_ID杀会话
    SELECT /+ rule / sid, s.serial#, 'kill -9 '||spid, event, blocking_session b_sess
    FROM v$session s, v$process p WHERE sql_id='&sql_id' AND s.paddr = p.addr order by 1;
    4.3.根据等待事件杀会话
    SELECT /+ rule / sid, s.serial#, 'kill -9 '||spid, event, blocking_session b_sess
    FROM v$session s, v$process p WHERE event='&event' AND s.paddr = p.addr order by 1;
    4.4.根据用户杀会话
    SELECT /+ rule / sid, s.serial#, 'kill -9 '||spid, event, blocking_session b_sess
    FROM v$session s, v$process p WHERE username='&username' AND s.paddr = p.addr order by 1;

    **5.性能报告收集与自动诊断报告(性能分析必备)
    5.1.statspack (提示:适合于9i以下版本)**
    spcreate.sql, execute statspack.snap
    spreport.sql spdrop.sql
    5.2.awr性能监控工具的使用方法(提示:10g/11g/12c/18c/19c使用)
    性能报告产生方法(支持txt和html格式):
    @$ORACLE_HOME/rdbms/admin/awrrpt.sql
    或者
    --RAC可以指定实例id
    @$ORACLE_HOME/rdbms/admin/awrrpti.sql
    5.3. addm自动故障诊断报告(提示:10g/11g/12c/18c/19c使用)
    @$ORACLE_HOME/rdbms/admin/addmrpt.sql
    或者
    --RAC可以指定实例id
    @$ORACLE_HOME/rdbms/admin/addmrpti.sql

    6.定期检查表空间使用情况(表空间100%导致业务异常)
    --from:www.fgedu.net.cn/oracle.html
    col f.tablespace_name format a15
    col d.tot_grootte_mb format a10
    col ts-per format a8
    select upper(f.tablespace_name) "TS-name",

       d.tot_grootte_mb "TS-bytes(m)",
       d.tot_grootte_mb - f.total_bytes "TS-used (m)",
       f.total_bytes "TS-free(m)",
       to_char(round((d.tot_grootte_mb - f.total_bytes) / d.tot_grootte_mb * 100,
                     2),
               '990.99') "TS-per"
         from (select tablespace_name,
               round(sum(bytes) / (1024 * 1024), 2) total_bytes,
               round(max(bytes) / (1024 * 1024), 2) max_bytes
          from sys.dba_free_space
         group by tablespace_name) f,
       (select dd.tablespace_name,
               round(sum(dd.bytes) / (1024 * 1024), 2) tot_grootte_mb
          from sys.dba_data_files dd
         group by dd.tablespace_name) d

    where d.tablespace_name = f.tablespace_name
    order by 5 desc;

    7.捕获占用CPU利用率过高的SQL语句
    set lin 1000
    set pagesize 1000
    col USERNAME format a16
    col MACHINE format a16
    col SQL_TEXT format a200
    SELECT a.username,a.machine,a.program,a.sid,a.serial#,a.status,c.piece,c.sql_text FROM v$session a,v$process b,v$sqltext c WHERE b.spid='&spid' AND b.addr=a.paddr AND a.sql_address=c.address(+) ORDER BY c.piece;

    8.查看等待事件(在数据库中首先要检查的操作)
    col event for a45
    SELECT inst_id,EVENT, SUM(DECODE(WAIT_TIME, 0, 0, 1)) "Prev", SUM(DECODE(WAIT_TIME, 0, 1, 0)) "Curr", COUNT(*) "Tot" , sum(SECONDS_IN_WAIT) SECONDS_IN_WAIT
    FROM GV$SESSION_WAIT
    WHERE event NOT
    IN ('smon timer','pmon timer','rdbms ipc message','SQL*Net message from client','gcs remote message')

    AND event NOT LIKE '%idle%'
    AND event NOT LIKE '%Idle%'
    AND event NOT LIKE '%Streams AQ%'

    GROUP BY inst_id,EVENT
    ORDER BY 1,5 desc
    提示:数据库中有一些常见异常等待事件,要重点分析,如:row cache lock、buffer busy waits、library cache lock、read by other session、latch:shared pool、gc buffer busy、cursor: pin S on X、direct path read、log file sync、enq: TX - index contention、latch free、enq: TX - row lock contention等等。

    9.根据等待事件查会话
    得到异常等待事件之后,我们就根据等待事件去查会话详情,也就是查看哪些会话执行哪些SQL在等待,另外还查出来用户名和机器名称,以及是否被阻塞。
    SELECT /+rule / sid, s.serial#, spid, event, sql_id, seconds_in_wait ws, row_wait_obj# obj,
    s.username, s.machine, BLOCKING_INSTANCE||'.'||blocking_session b_sess
    FROM v$session s, v$process p
    WHERE event='&event_name' AND s.paddr = p.addr order by 6;
    10.查询某个会话详情
    得到会话列表之后,可以根据如下SQL查询某个会话的详细信息,如上次个执行的SQL_ID,登录时间等。
    SELECT s.sid, s.serial#, spid, event, sql_id, PREV_SQL_ID, seconds_in_wait ws, row_wait_obj# obj,
    s.username, s.machine, module,blocking_session b_sess,logon_time
    FROM v$session s, v$process p
    WHERE sid = '&sid' AND s.paddr = p.addr
    11.查询对象信息
    从前面两个SQL都可以看到会话等待的对象ID,可以通过如下SQL查询对象的详细信息。
    col OBJECT_NAME for a30
    select owner,object_name,subobject_name,object_type
    from dba_objects
    where object_id=&oid
    12.根据SQL_ID、HASH_VALUE查询SQL语句
    select sql_id,SQL_fullTEXT
    from v$sqlarea
    where (sql_id='&sqlid' or hash_value=to_number('&hashvale') )
    and rownum<2
    13..查询会话阻塞情况,某个会话阻塞了多少个会话
    select count(*),blocking_session
    from v$session
    where blocking_session is not null
    group by blocking_session;
    **14.查询数据库的锁
    通过如下SQL查询某个会话的锁,有哪些TM、TX锁,以及会话和锁关联查询的SQL。**
    set linesize 180
    col username for a15
    col owner for a15
    col OBJECT_NAME for a30
    col SPID for a10
    14.1.查询某个会话的锁
    select /+rule/SESSION_ID,OBJECT_ID,ORACLE_USERNAME,OS_USER_NAME,PROCESS,LOCKED_MODE
    from gv$locked_object where session_id=&sid;
    14.2.查询TM、TX锁
    select /+rule/* from v$lock
    where ctime >100 and type in ('TX','TM') order by 3,9;
    14.3.查询数据库中的锁
    select /+rule/s.sid,p.spid,l.type,round(max(l.ctime)/60,0) lock_min,s.sql_id,s.USERNAME,b.owner,b.object_type,b.object_name
    from v$session s, v$process p,v$lock l,v$locked_object o,dba_objects b
    where o.SESSION_ID=s.sid and s.sid=l.sid and o.OBJECT_ID=b.OBJECT_ID
    and s.paddr = p.addr and l.ctime >100 and l.type in ('TX','TM','FB')
    group by s.sid,p.spid,l.type,s.sql_id,s.USERNAME,b.owner,b.object_type,b.object_name
    order by 9,1,3
    15.故障信息收集
    提示:数据库hang住了之后,需要详细分析原因,或者提供给二线支持的信息,可使用下面脚本,收集systemstate dump和hanganalyze信息,如果有sqlplus无法登陆的情况,可以加-prelim参数。
    --systemstate dump
    sqlplus -prelim / as sysdba
    oradebug setmypid
    oradebug unlimit;
    oradebug dump systemstate 266;
    --wait for 1 min
    oradebug dump systemstate 266;
    --wait for 1 min
    oradebug dump systemstate 266;
    oradebug tracefile_name;
    --hanganalyze
    oradebug setmypid
    oradebug unlimit;
    oradebug dump hanganalyze 3
    --wait for 1 min
    oradebug dump hanganalyze 3
    --wait for 1 min
    oradebug dump hanganalyze 3
    oradebug tracefile_name

    ]]>
    Sentinel 1.8.0 年度版本发布,熔断降级重构升级-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在经过数月的打磨后,Sentinel 1.8.0 版本正式发布!该版本是本年度最重要的版本之一,包含大量特性改进与 bug 修复,尤其是针对熔断降级特性的完善升级(支持任意统计时长、慢调用比例降级策略、熔断器事件监听);同时该版本进一步扩充了开源生态,提供对 Java EE (JAX-RS, CDI), Quarkus, HTTP client 等体系的原生支持。详细特性列表请参考 Release Notes,欢迎大家使用并提出建议。

    下面我们来一起探索一下 Sentinel 1.8.0 的重要特性。

    熔断降级改进

    一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

    现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务进行自动熔断,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。

    Sentinel 1.8.0 版本对原有的熔断降级模块进行了重构和升级,重新以熔断器(cicuit breaker)的形式进行抽象,并进一步完善了熔断器的能力。新版熔断降级支持任意统计时长,用户可以根据接口的场景灵活配置统计维度为秒级或者分钟级;同时我们也引入了用户需要的半开启探测恢复支持。新版熔断降级还对原有的秒级平均 RT 策略进行了升级,原有 RT 策略对稀疏请求不友好,并且采用平均 RT 可能会被某个特别慢的调用影响。1.8.0 版本将基于响应时长的策略升级为慢调用比例策略,用户指定响应时长超出多少记为慢调用(即稳态 RT 的上界),同时配置慢调用比例阈值,结合场景配置统计时长维度,即可更好地针对慢调用进行熔断。用户可以结合 Sentinel 控制台的实时监控来决定稳态 RT 的阈值。

    同时考虑到用户可能需要感知熔断器的状态变化以进行一些日志记录或其它的操作,Sentinel 提供了熔断器的事件监听器扩展,用户可以注册自定义的事件监听器以感知熔断器状态变化。示例:

    EventObserverRegistry.getInstance().addStateChangeObserver("logging",

    (prevState, newState, rule, snapshotValue) -> {
        if (newState == State.OPEN) {
            // 变换至 OPEN state 时会携带触发时的值
            System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                TimeUtil.currentTimeMillis(), snapshotValue));
        } else {
            System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                TimeUtil.currentTimeMillis()));
        }
    });

    至此,Sentinel 已提供三种熔断策略:慢调用比例、异常比例和异常数。有关熔断降级特性的更多信息请参考 熔断降级文档。

    开源生态与云原生

    Sentinel 1.8.0 进一步扩充了开源生态。Sentinel 1.8.0 引入了 Java EE 原生支持,提供对 JAX-RS Web 应用的原生支持(sentinel-jax-rs-adapter),以及基于 CDI 的注解埋点支持(sentinel-annotation-cdi-interceptor),对于采用原生 Java EE 架构的服务可以更方便地接入。

    image.png
    Quarkus 作为广受关注的云原生微服务框架,在微服务框架中活跃度排名前列。Sentinel 1.8.0 提供了针对 Quarkus 的适配模块,支持 Quarkus Web 服务无缝集成(基于 JAX-RS 适配),并且通过 CDI 注解埋点支持和 Reactor 适配,可以针对 Quarkus 服务中的任意逻辑进行流控。Quarkus 适配模块支持构建 native image,感兴趣的开发者欢迎参考 demo 进行尝试。

    至此,Sentinel 的开源生态得到进一步扩充:
    image.png

    其它重要特性/改进

    • @SentinelResource 注解支持配置类级别统一的 defaultFallback
      修复 Dubbo 2.7.x 适配模块 Entry 泄漏可能导致 FGC 的 bug

    修复 Spring Web 适配模块在内部转发请求时可能导致 ErrorEntryFreeException 的 bug
    支持通过 properties 配置文件配置 project.name(至此所有启动配置项均可通过文件配置)
    新增 Eureka 数据源支持
    更多信息请参考 Release Notes。

    Start hacking

    Sentinel 1.8.0 是社区一起定义的年度版本,近 80% 的特性都是社区开发者贡献的。感谢各位贡献者的付出!同时我们非常欢迎大家持续参与社区贡献,一起来参与未来版本的演进。若您有意愿参与社区贡献,欢迎联系我们加入 Sentinel 贡献小组一起成长(Sentinel 开源讨论钉钉群:30150716)。我们会定期给活跃贡献者寄送小礼品,核心贡献者会提名为 committer,一起主导社区的演进。同时,也欢迎大家通过 AHAS Sentinel 控制台 来快速体验 Sentinel 的能力。Now let's start hacking!

    ]]>
    8月27日 Spark 社区直播【OAP Spark 优化介绍: 通过索引和缓存优化交互式查询性能】-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 主题:

    OAP Spark 优化介绍: 通过索引和缓存优化交互式查询性能

    直播时间:

    8月27日 19:00

    观看方式:

    届时进入直播间(回看链接也是这个):https://developer.aliyun.com/live/43848?spm=5176.8068049.0.0.27366d19Q1XzyT

    或扫描下方钉钉群二维码进群观看

    讲师介绍:

    陈海锋,英特尔亚太研发有限公司大数据部门的高级软件架构师,开发经理,主要研究和关注基于Hadoop和Spark的大数据框架的分析和优化,Apache社区的长期贡献者。

    沈祥翔,英特尔亚太研发有限公司大数据部门的高级软件工程师,主要担任OAP项目的开发。

    直播介绍:

    简单介绍OAP的总体蓝图。同时详细介绍其中的一个具体优化,使用索引和缓存来解决交互式查询性能挑战。英特尔和社区合作,为Spark SQL实现了索引和数据源缓存,通过为关键查询列创建并存储完整的B +树索引,并使用智能的细粒度数据缓存策略,我们可以极大的提升基于Spark SQL的交互式查询的性能。

    8.27直播.png

    ]]>
    消息链路优化之弱感知链路优化-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:闲鱼技术-逸昂

    一、消息系统面临的问题

    闲鱼的消息系统作为沟通买家与卖家的工具,增进理解、促进信任,对闲鱼的商品成交有重要的价值,是提升用户体验最关键的环节。
    闲鱼消息系统面临的问题包括:在线消息的体验提升、离线推送的到达率、消息玩法与消息底层系统的耦合过强。
    现阶段离线推送的问题最为关键,对用户体验影响较大,本文优先解决离线推送的到达问题。

    二、消息系统现状

    2.1 消息系统链路区分

    根据闲鱼客户端是否在线,闲鱼的整体消息链路大致可以分为强感知链路和弱感知链路。
    强感知链路由发送方客户端、idleapi-message(闲鱼的消息网关)、heracles(闲鱼的消息底层服务)、accs(阿里自研的长连接通道)、接收方客户端组成。整条链路的核心指标在于端到端延迟和消息到达率。强感知链路的双方都是在线的,消息到达客户端就可以保证接收方感知到。强感知链路的主要痛点在消息的端到端延迟。
    弱感知链路与强感知链路的主要不同在于:弱感知链路的接收方是离线的,需要依赖离线推送这样的方式送达。因此弱感知链路的用户感知度不强。其核心指标在于消息的到达率,而非延迟。当前弱感知链路的重点是到达率仍有提升空间。

    2.3 闲鱼的消息架构

    这里放一张闲鱼消息系统的架构图,大家感受下整体链路:
    图片.png
    HSF是一个远程服务框架,是dubbo的内部版本。
    tair是阿里自研的分布式缓存框架,支持 memcached、Redis、LevelDB 等不同存储引擎。
    agoo是阿里的离线推送中台,负责整合不同厂商的离线推送通道,向集团用户提供一个统一的离线推送服务。
    accs是阿里自研的长连接通道,为客户端、服务端的实时双向交互提供便利。
    lindorm是阿里自研的NoSQL产品,与HBase有异曲同工之妙。
    域环是闲鱼消息优化性能的核心结构,用来存储用户最新的若干条消息。

    强感知链路和弱感知链路在通道选择上是不同的。强感知链路使用accs这个在线通道。弱感知链路使用agoo这个离线通道。

    三、弱感知链路问题分析

    链路这件事情,比较抽象。首先需要分析链路的核心指标、链路的环节、链路的瓶颈,然后才能着手优化。问题分析清楚,也就成功了一半。

    3.1 何为弱感知

    本期先带大家看看闲鱼的弱感知链路优化。弱感知链路是指闲鱼的离线推送系统。
    相比较于在线消息和端内推送,离线推送难以确保被用户感知到。典型的情况包括:

    • 未发送到用户设备。因为离线通道的token失效、参数错误、用户关闭应用通知、用户已卸载等原因,推送未送达用户设备。这种情况可以从通道的返回分析。
    • 发送到用户设备但没有展示到系统通知栏。闲鱼曾遇到通道返回成功,但是用户未看到推送的案例。
    • 展示到通知栏,并被系统折叠。不同安卓厂商对推送的折叠策略不同,折叠的原因包括:点击率、应用在厂商处的权重、推送的数量等。推送被折叠后,需要用户主动展开才能看到推送内容,触达效果会明显变差。
    • 展示到通知栏,并被用户忽略。离线推送的点击率相比于在线推送更低,原因包括:用户不愿意查看推送;用户看到了推送,但是对内容不感兴趣;用户在忙别的事,无暇处理。

    离线推送用户感知度不高,闲鱼称之为弱感知链路。

    3.2 弱感知链路的构成

    闲鱼的弱感知链路分为系统、通道、用户三个部分,包含Hermes、agoo、厂商、设备、用户、承接页这几个环节。

    从推送的产生到用户最终进入闲鱼,共分为如下几个步骤:
    Step 1. Hermes是闲鱼的用户触达系统,负责人群管理、内容管理、时机把控,是整个弱感知链路的起点。
    Step 2. agoo是阿里内部承接离线推送的中台,是闲鱼离线推送能力的基础。
    Step 3. agoo实现离线推送依靠的是厂商的推送通道,例如:苹果的apns通道、Google的fcm通道、以及国内各大厂商的自建通道。
    Step 4. 通过厂商的通道,推送最终出现在用户的设备上,这是用户能感知到推送的前提条件。
    Step 5. 如果用户刚巧看到这条推送,推送的内容也很有趣,在用户的主动点击下。会唤起闲鱼,打开承接页,进而给用户展示个性化的商品。到此,弱感知链路就完成了使命。

    3.3 弱感知链路面临的问题

    弱感知链路的核心问题在于:

    • 推送的消息是否投递给了用户。
    • 用户是否有感知

    这对应推送的两个阶段:

    • 推送到达设备
    • 用户查看推送并点击

    其中,到达设备这个阶段是最基础的,也是本次优化的核心。
    我们可以将每一步的消息处理量依次平铺,展开为一张漏斗图,从而直观的查看链路的瓶颈。漏斗图斜率最大的地方是优化的重点,差异小的地方不需要优化。
    图片.png
    通过分析闲鱼的漏斗图,弱感知链路的优化重点在三个方面:

    • agoo受理率。agoo受理率是闲鱼发送推送的数量到可以通过agoo(阿里承接离线推送的中台)转发到厂商通道的数量之间的漏斗。
    • 厂商受理率。厂商受理率是agoo中台受理的量到厂商返回成功的量之间的漏斗。
    • Push点击

    有了优化方向,我们来看看优化手段吧。

    四、弱感知链路优化

    我们跟随推送的视角,顺着链路看下闲鱼的优化。

    4.1 agoo受理率优化

    用户的推送,从 Hermes 站点搭乘“班车”,驶向下一站: agoo。这是推送经历的第一站,到站一看,傻眼了,只有不到一半的推送到站下车了。这是咋回事嘞?
    这就要先说说 agoo 了,调用 agoo 有两种方式:

    • 指定设备和客户端,agoo直接将推送投递到相应的设备
    • 指定用户和客户端,agoo根据内部的转换表,找到用户对应的设备,再进行投递。

    闲鱼不保存用户的设备信息。因此,闲鱼是按照用户来调用agoo的。同时,因为由于没有用户的设备信息,并不知道用户是 iOS 客户端还是 Android 客户端。工程侧不得不向 iOS 和 Android 都发送一遍推送。虽然保证了到达,但是,一半的调用都是无效的。
    为了解这个问题,我们使用了agoo的设备信息。将用户转换设备这一阶段提前到了调用 agoo 之前,先明确用户对应的设备,再指定设备调用 agoo,从而避免无效调用。
    图片.png
    agoo调用方式优化后,立刻剔除了无效调用,agoo受理率有了明显提升。至此,我们总算能对 agoo 受理失败的真正原因做一个高大上的分析了。根据统计,推送被 agoo 拒绝的主要原因是:用户关闭了通知权限
    同时,对 agoo 调用数据的进一步分析发现。有部分用户找不到对应的设备。
    优化到此,我们猛然发现多了两个问题。那就优化呗:

    • 通知体验优化,引导打开通知权限
    • 与agoo共建设备库,解决设备转换失败的问题

    这两个优化方向又是一片新天地,我们择日再聊。

    4.2 厂商受理率优化

    推送到达 agoo ,分机型搭乘厂商“专列”,驶向下一站:用户设备。这是推送经历的第二站。出站查票,发现竟然超员了。于是乎,闲鱼每天有大量推送因为超过厂商设定的限额被拦截。
    提供推送通道的厂商,为了保证用户体验,会对每个应用能够推送的消息总量进行限制。这个限制会根据推送的类型和应用的用户规模设定。
    推送主要分为产品类的推送和营销类的推送。对于产品类推送,厂商会保证到达;对于营销类推送,厂商会进行额度限制。未标记的推送默认作为营销类推送对待。
    闲鱼刚好没有对推送进行标记,因此触发了厂商的推送限制。这对闲鱼的用户来说,会带来困扰。闲鱼的交易,很依赖买卖家之间的消息互动。这部分消息是需要确保到达的。同样,订单类的消息、用户的关注,也需要保证推送给用户。
    根据主流厂商的接口协议,闲鱼将Push分为即时通讯消息、订单状态变化、用户关注内容、营销消息这几类,并进行相应标记。
    同时,在业务上,我们也进行了推送的治理。将用户关注度不高的消息,取消推送,避免打扰。
    经过这些优化,闲鱼因为超过厂商限额而被拦截的推送实现了清零。

    4.3 Push点击优化

    在日常的开发测试过程中,闲鱼的工程师们发现了推送的两个体验问题:

    • 用户点击Push有开屏广告
    • 营销Push 也有权限校验,更换用户登陆后无法点击

    对于开屏广告功能,闲鱼增加了Push点击跳广告的能力。针对Push的权限校验功能,闲鱼根据场景做了细分。涉及个人隐私的推送,保持权限校验不变。营销类的推送,放开权限校验。
    以上是点击体验的优化,我们还需要考虑用户的点击意愿。
    用户点击量与推送的曝光量、推送素材的有趣程度相关。推送的曝光量又和推送的到达量、推送的到达时机有关。通过优化agoo受理率、厂商受理率,我们解决了推送到达量的瓶颈。在推送内容上,我们需要优化的是推送的时机和相应的素材。
    在推送时机上,算法会根据用户的偏好和个性化行为数据,计算每个用户的个性化推送时间,在用户空闲的时间推送。避免在不合适的时间打扰用户,同时也能提升用户看到推送的可能性。
    在推送素材上,算法会根据素材的实时点击反馈,对素材做实时赛马。只发用户感兴趣的素材,提高用户点击意愿。

    五、链路优化效果

    通过分析链路的瓶颈并进行相对应的优化,整体链路有了不错的提升,到达率相对提升了两位数。

    六、聊后续

    今天主要和大家聊一下消息链路中的一环:弱感知链路的优化。
    消息系统整体,还是一个比较复杂的领域。闲鱼在消息的发展过程中,面临着如下问题:

    • 如何进行消息的链路追踪
    • 如何保证IM消息的快速到达
    • 如何将消息的玩法和底层能力分离
    • 离线推送中如何通过用户找到对应的设备

    在后续的文章中,我们会和大家一一讨论这些主题。预知后事如何,且听下回分解。

    ]]>
    Cassandra4.0版本新功能揭秘-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 8月份的社区线上开发者活动,邀请到的演讲嘉宾蔡一凡老师是Cassandra贡献者之一,其所在公司部署了全球最大的Cassandra集群节点数量。
    本次演讲介绍了Cassandra 4.0版本中的新功能:审计日志、零拷贝串流、Netty节点间通信、虚拟表、增量式修复、临时副本等。
    1、审计日志:
    将数据库所有操作记录到一个本地文件,包括authentication,所有的CQL请求,不论成功与否都会被记录下来。用途广泛,如利用记录来debug线上问题,也可以辅助测试。4.0版本中同步推出的full query logger就是用审核日志来实现的,这些由full query logger生成的记录可用来做回放测试。另外,也可以帮助企业做合规管理,可作为企业的审核依据。
    2、零拷贝串流:
    零拷贝串流是指在串流时无需将数据读到内存后再写入到网络,发送方和接收方可以直接通过网络发送和接收数据。从而显著提升性能(3-5倍的速度),远小于普通串流所需的时间,并大大减少内存和CPU的占用。在Cassandra中,串流会被应用于很多场景。基本所有数据在集群中被复制时都可用到。
零拷贝串流可帮助缩短节点的恢复时间,降低多个节点同时处于不可用状态的概率。可以帮助降低运维成本。如加速节点数据迁移速率。
    3、Netty节点间通信
    在4.0版本中,节点间通信改成了Netty。I/O是非阻塞的,不再按节点分配线程。
    Netflix通过测试192个节点的Cassandra集群,对比4.0和3.0版本,发现4.0版本的延迟平均值减少40%,99分位的延迟减少了60%,吞吐量提升约2倍。
    另外,节点间加密通信扩展性更高,主要是得益于Netty的tcnative。它跟JDK自带的加密性能相比,大概提升了有4倍。
    4、虚拟表
    虚拟表是基于Cassandra内部的一些API实现的,可以把虚拟表当作Cassandra的一个接口。目前虚拟表是只读的。每一个虚拟表都是每个节点所特有的,也就是说虚拟表是local的。有了虚拟表,我们可以不用JMX,通过CQL来进行查询虚拟表,从而获取Cassandra的系统状态和当前配置。
    5、增量式修复
    增量式修复在2.1版本中就已推出。但这个功能存在一些问题,不适用于生产环境。Cassandra 4.0把之前的很多问题修复了。增量式修复将数据分为“已修复”和“未修复”两个部分,每次修复时只修复“未修复”的部分,不再修复已修复过的数据。从而减少每次修复的时间,只需要几分钟即可完成。
    6、临时副本
    临时副本是4.0版本的试验性功能,并不推荐用在生产环境中。临时副本节点只保存没有修复的数据,在修复之后,这些数据就会被临时副本节点删除。这个功能最好是和增量式修复一起使用,这样可以很快地从临时副本节点中将未修复的数据修复,之后再删除。这样一来,可以认为临时节点并不占用存储空间。
    临时副本节点带来的好处是减少存储空间,临时节点只保留临时数据,数据量比较小,修复完就删除,所以这个节点会使用更少的CPU和I/O。

    ]]>
    MongoDB Certified DBA 经验分享-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Introduction

    MongoDB 官方认证包括两个类型,

    • Certified DBA
    • Certified Developer

    DBA 强调系统的管理,Developer 强调对 MongoDB 的使用,比如要对 Aggregation 的各种 Operator 的使用很熟悉,但是也有很多共性的知识点,比如 CRUD,Index 等,我们选择 Certified DBA 即可。

    完成任何一个认证,需要进行线上的考试,考试时长 90 分钟,需要准备一个单独房间和干净的桌面,不能用任何参考资料(网页,手抄等),监考老师会通过视频会议软件和你沟通,全程需要录屏,并要求你的正面头像要一直处于摄像头范围内,还是比较严格的。

    每个月会有一个考试时间段,称为一个 Session,每个 Session 大约是 10 天,报名交 150 美金,可以约接下来 Session 的任何一天来参加考试,详细的注册步骤根据邮件流程走即可,需要注意的是预约的时间要选好时区,在中国的话选择 Taipei 即可。另外,考试如果没通过是不退费的。

    注册地址:https://university.mongodb.com/certification/dba/about

    Learning Path

    Certified DBA 主要有两个学习资料,一个是 MongoDB University 的 DBA 课程,如下,

    • M001: MongoDB Basics
    • M103: Basic Cluster Administration
    • M201: MongoDB Performance
    • M310: MongoDB Security
    • M312: Diagnostics & Debugging

    另外一个是,官方给的 MongoDB Certification Exam Study Guide ,这个 study guide 的优点是全面,涵盖考试涉及的所有知识点,但是内容非常多,而且全部看文档也比较无聊,对知识理解也不深刻,所以我的建议是先学习上面的视频课程,跟着课程走一遍,抓住重点,每门课程中间会穿插一些小考试,结束的时候会有一个最终考试,6 个选择题,考试通过会给你发一个电子证书,也建议大家都通过一下。

    课程学习时间参考:前两个比较基础,每门差不多2-3 个小时应该就可以全过一遍,后面三门课程细节比较多一些,预计每门课程需要 5-6 个小时。

    课程学完之后, MongoDB Certification Exam Study Guide 就可以发挥作用了,可以根据学习的课程,把这个 study guide 快速整体过一遍,看看还有没有不熟悉的知识点,适当做些笔记,后面再回顾几次,知识部分的学习我觉得就足以通过这个考试了。

    这里共享一下,我自己记录的笔记,这个笔记只记录了我在看 study guide 对应的 documentation 的时候不熟悉的知识点,从最后的实际考试来看,有些知识点可能超出了考试的范围,仅供参考,

    MongoDB DBA 认证知识 Review.pdf

    Exam

    考试全部是选择题,总共 60 个,大部分为多选,得分要求是 490 分以上才能通过,但是考试的总分以及每个题的评分标准并没有公布,所以目前是不清楚的,题目按 study guide 分类,最后如果通过的话,给出的考试结果,也是各个分类题目正确的百分比,没有具体的分数,下面是我的一个参考结果,

    1597735671851-cde6c77e-6460-4a1f-bb57-84ff301884b6.png

    在知识部分学习完成之后,大家可以先去试一下 Practice Exam (https://university.mongodb.com/certification/exam-prep ),熟悉一下考试操作和题目分类,另外真正的考试也遇到几个题和 Practice Exam 高度相似,这个 Practice Exam 还是值得去多试几次的。

    说下正式考试的流程,按预约的时间提前 15 分钟找个会议室,然后点 Start Exam,会有 Proctor 跟你视频,确认你的 ID 和设置的安全提问,所以身份证要记得带着,然后监考会确认考试环境没有干扰,就可以开始考试了,上述流程都是在一个第三方的网站,但是当真正考试的时候,就还是在 MongoDB 自己的网站,和你 Practice Exam 的环境一致。

    最后,Good Luck。

    ]]>
    mysql读写分离1 -- mysql安装-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 安装前先删除残留:
    [root@izwz9hy3mj62nle7573jv5z ~]# rpm -qa | grep -i mysql
    [root@izwz9hy3mj62nle7573jv5z ~]# 
    [root@izwz9hy3mj62nle7573jv5z ~]# find / -name mysql
    /root/.jenkins/local_lib/mysql
    /etc/selinux/targeted/active/modules/100/mysql
    /usr/lib64/mysql
    /usr/share/mysql
    /usr/bin/mysql
    [root@izwz9hy3mj62nle7573jv5z ~]# 

    安装:

    下载地址:https://downloads.mysql.com/archives/community/

    # 下载
    wget  https://downloads.mysql.com/archives/get/p/23/file/mysql-community-server-5.7.23-1.el7.x86_64.rpm
    # 会通过rpm自动下载并且覆盖强制安装
    rpm -ivh mysql-community-server-5.7.23-1.el7.x86_64.rpm --force --nodeps

    卸载--rpm安装方式的卸载:

    # 卸载
    rpm -e xxxxxXXX.rpm

    卸载--yum安装方式的卸载:

    yum remove mariadb*
    yum remove mysql*

    访问数据库:

    mysql -uroot -p

    创建用户和授权:

    -- 使用命令登录:mysql -u root -p
    -- 创建用户 用户名:long 密码:Long@123456
    create user 'long'@'%' identified by 'Long@123456';
    -- 授权 *.*表示所有库
    grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'long'@'%' identified by 'Long@123456';
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;

    忘记密码:

    # 配置跳过授权
    [root@izwz9hy3mj62nle7573jv5z ~]# vim /etc/my.cnf
    # 如果忘记密码,配置skip-grant-tables,实现访问后跳过授权
    [mysqld]
     skip-grant-tables
    
    # 重启mysql,再次登陆无需 

    重设密码:

    flush privileges;
    set password for root@localhost = password('123456');
    flush privileges;

    重设密码后注释掉skip-grant-talbes

    创建用户和授权:

    -- 使用命令登录:mysql -u root -p
    -- 创建用户 用户名:canal 密码:Canal@123456
    create user 'canal'@'%' identified by 'Canal@123456';
    -- 授权 *.*表示所有库
    grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'canal'@'%' identified by 'Canal@123456';

    重启:

    service mysqld restart
    ]]>
    Elasticsearch 既是搜索引擎又是数据库?真的有那么全能吗?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    image.png

    作者介绍:李猛(ynuosoft),Elastic-stack 产品深度用户,ES 认证工程师,2012 年接触 Elasticsearch,对 Elastic-Stack 开发、架构、运维等方面有深入体验,实践过多种 Elasticsearch 项目,最暴力的大数据分析应用,最复杂的业务系统应用;业余为企业提供 Elastic-stack 咨询培训以及调优实施。

    Elasticsearch 认知

    Elasticsearch 是什么

    Elasticsearch 是什么,不同的人有不同的理解定位,之前写过 Elasticsearch 对比其它数据产品的文章《 Elasticsearch 对垒8大竞品技术,孰优孰劣?》,看了文章下面的评论,很多人定位它是搜索引擎,我觉得也很片面,下面就谈谈我的认知:

    1)Elasticsearch 是搜索引擎

    Elasticsearch 在搜索引擎数据库领域排名绝对第一,内核基于 Lucene 构建,支持全文搜索是职责所在,提供了丰富友好的 API。个人早期基于 Lucene 构建搜索应用,需要考虑的因素太多,自接触到 Elasticsearch 就再无自主开发搜索应用。普通工程师要想掌控 Lucene 需要一些代价,且很多机制并不完善,需要做大量的周边辅助程序,而 Elasticsearch 几乎都已经帮你做完了。

    2)Elasticsearch 不是搜索引擎

    说它不是搜索引擎,估计很多从业者不认可,在个人涉及到的项目中,传统意义上用 Elasticsearch 来做全文检索的项目占比越来越少,多数时候是用来做精确查询加速,查询条件很多,可以任意组合,查询速度很快,替代其它很多数据库复杂条件查询的场景需求;甚至有的数据库产品直接使用 Elasticsearch 做二级索引,如 HBase、Redis 等。Elasticsearch 由于自身的一些特性,更像一个多模数据库。

    image.png

    图示:Elasticsearch 综合数据库排名热度已经到第 7

    3)Elasticsearch 是数据库

    Elasticsearch 使用 Json 格式来承载数据模型,已经成为事实上的文档型数据库,虽然底层存储不是 Json 格式,同类型产品有大名鼎鼎的 MongoDB,不过二者在产品定位上有差别,Elasticsearch 更加擅长的基于查询搜索的分析型数据库,倾向 OLAP;MongoDB 定位于事务型应用层面 OLTP,虽然也支持数据分析,笔者简单应用过之后再无使用,谁用谁知道。

    4)Elasticsearch 不是数据库

    Elasticsearch 不是关系型数据库,内部数据更新采用乐观锁,无严格的 ACID 事务特性,任何企图将它用在关系型数据库场景的应用都会有很多问题,很多其它领域的从业者喜欢拿这个来作为它的缺陷,重申这不是 Elasticsearch 的本质缺陷,是产品设计定位如此。

    image.png

    Elasticsearch 做什么

    Elasticsearch 虽然是基于 Lucene 构建,但应用领域确实非常宽泛。

    1)全文检索

    Elasticsearch 靠全文检索起步,将 Lucene 开发包做成一个数据产品,屏蔽了 Lucene 各种复杂的设置,为开发人员提供了很友好的便利。很多传统的关系型数据库也提供全文检索,有的是基于 Lucene 内嵌,有的是基于自研,与 Elasticsearch 比较起来,功能单一,性能也表现不是很好,扩展性几乎没有。
    如果,你的应用有全文检索需求,建议你优先迁移到 Elasticsearch 平台上来,其提供丰富的 Full text queries 会让你惊讶,一次用爽,一直用爽。

    image.png

    2)应用查询

    Elasticsearch 最擅长的就是查询,基于倒排索引核心算法,查询性能强于 B-Tree 类型所有数据产品,尤其是关系型数据库方面。当数据量超过千万或者上亿时,数据检索的效率非常明显。

    个人更看中的是 Elasticsearch 在通用查询应用场景,关系型数据库由于索引的左侧原则限制,索引执行必须有严格的顺序,如果查询字段很少,可以通过创建少量索引提高查询性能,如果查询字段很多且字段无序,那索引就失去了意义;相反 Elasticsearch 是默认全部字段都会创建索引,且全部字段查询无需保证顺序,所以我们在业务应用系统中,大量用 Elasticsearch 替代关系型数据库做通用查询,自此之后对于关系型数据库的查询就很排斥,除了最简单的查询,其余的复杂条件查询全部走 Elasticsearch。

    3)大数据领域

    Elasticserach 已经成为大数据平台对外提供查询的重要组成部分之一。大数据平台将原始数据经过迭代计算,之后结果输出到一个数据库提供查询,特别是大批量的明细数据。

    这里会面临几个问题,一个问题是大批量明细数据的输出,如何能在极短的时间内写到数据库,传统上很多数据平台选择关系型数据库提供查询,比如 MySQL,之前在这方面吃过不少亏,瞬间写入性能极差,根本无法满足要求。另一个问题是对外查询,如何能像应用系统一样提供性能极好的查询,不限制查询条件,不限制字段顺序,支持较高的并发,支持海量数据快速检索,也只有 Elasticsearch 能够做到比较均衡的检索。

    从官方的发布版本新特性来看,Elasticseacrch 志在大数据分析领域,提供了基于列示存储的数据聚合,支持的聚合功能非常多,性能表现也不错,笔者有幸之前大规模深度使用过,颇有感受。

    Elasticsearch 为了深入数据分析领域,产品又提供了数据 Rollup 与数据 Transform 功能,让检索分析更上一层楼。在数据 Rollup 领域,Apache Druid 的竞争能力很强,笔者之前做过一些对比,单纯的比较确实不如 Druid,但自 Elasticsearch 增加了 Transfrom 功能,且单独创建了一个 Transfrom 的节点角色,个人更加看好 Elasticseach,跳出了 Rollup 基于时间序列的限制。

    image.png

    4)日志检索

    著名的 ELK 三件套,讲的就是 Elasticsearch,Logstash,Kibana,专门针对日志采集、存储、查询设计的产品组合。很多第一次接触到 Elasticsearch 的朋友,都会以为 Elasticsearch 是专门做日志的,其实这些都是误解,只是说它很擅长这个领域,在此领域大有作为,名气很大。

    日志自身特点没有什么通用的规范性,人为的随意性很大,日志内容也是任意的,更加需求全文检索能力,传统技术手段本身做全文检索很是吃力。而 Elasticsearch 本身起步就是靠全文检索,再加上其分布式架构的特性,非常符合海量日志快速检索的场景。今天如果还发现有IT从业人员用传统的技术手段做日志检索,应该要打屁股了。

    如今已经从 ELK 三件套发展到 Elastic Stack 了,新增加了很多非常有用的产品,大大增强了日志检索领域。

    5)监控领域
    指标监控,Elasticsearch 进入此领域比较晚,却赶上了好时代,Elasticsearch 由于其倒排索引核心算法,也是支持时序数据场景的,性能也是相当不错的,在功能性上完全压住时序数据库。

    Elasticsearch 搞监控得益于其提供的 Elastic Stack 产品生态,丰富完善,很多时候监控需要立体化,除了指标之外,还需要有各种日志的采集分析,如果用其它纯指标监控产品,如 Promethues,遇到有日志分析的需求,还必须使用 Elasticsearch,这对于技术栈来说,又扩增了,相应的掌控能力会下降,个人精力有限,无法同时掌握很多种数据产品,如此选择一个更加通用的产品才符合现实。

    image.png

    6)机器学习

    机器学习最近几年风吹的很大,很多数据产品都集成了,Elasticsearch 也必须有,而且做的更好,真正将机器学习落地成为一个产品 ,简化使用,所见所得;而不像其它数据产品,仅仅集成算法包,使用者还必须开发很多应用支持。

    Elasticsearch 机器学习提供了两种方式,一种是异常检测类型,属于无监督学习,采用聚类模型,通常应用在安全分析领域,检测异常访问等;一种是数据帧分析,属于分类与回归,属于监督学习,可用于在业务模型领域,如电商行业,价格模型分析。

    Elasticsearch 本身是数据平台,集成了部分机器学习算法,同时又集成了 Kibana 可视化操作,使得从数据采集、到模型训练、到模型预测应用都可以一键式完成。

    Elasticserach 提供的机器学习套件,个人认为最应该应用在数据质量这个领域,帮助大数据平台自动检测数据质量,从而降低人力提供效率。

    image.png

    需求等级

    Elasticsearch 整个的技术栈非常复杂,涉及到的理论与技术点非常多,完全掌握并不现实,作为一个 IT 从业者,首先是定位好自己的角色,依据角色需求去学习掌握必备的知识点。以下是笔者对于一个技术产品的划分模型:

    1、概念

    Elasticsearch 涉及到的概念很多,核心概念其实就那么几个,对于一个新手来说,掌握概念目的是为了建立起自己的知识思维模型,将之后学习到的知识点做一个很好的归纳划分;对于一个其它数据产品的老手来说 ,掌握概念的目的是为了与其它数据产品划分比较,深入的了解各自的优劣,在之后工作中若有遇到新的业务场景,可以迅速做出抉择。

    IT 从业者普遍都有个感受,IT 技术发展太快了,各种技术框架产品层出不穷,学习掌握太难了,跟不上节奏。其实个人反倒觉得变化不大,基础理论核心概念并没有什么本质的发展变化,无非是工程技术实操变了很多,但这些是需要深入实践才需要的,对于概念上无需要。

    作为一个技术总监,前端工程师工作 1~2 年的问题都可以问倒他,这是大家对于概念认知需求不一样。

    image.png

    2、开发

    开发工程师的职责是将需求变成可以落地运行的代码。Elasticsearch 的应用开发工作总结起来就是增删改查,掌握必备的 Elasticsearch REST API,熟练运用足以。笔者之前任职某物流速运公司,负责 Elasticsearch 相关的工作,公司 Elasticsearch 的需求很多,尤其是查询方面,Elasticsearch 最厉害的查询是 DSL,这个查询语法需要经常练习使用,否则很容易忘记,当每次有人询问时,都安排一个工程师专门负责各种解答,他在编写 DSL 方面非常熟练,帮助了很多的工程师新手使用 Elasticsearch,屏蔽了很多细节,若有一些难搞定的问题,会由我来解决,另外一方面作为负责人的我偶然还要请他帮忙编写DSL。

    Elasticsearch 后面提供了 SQL 查询的功能,但比较局限,复杂的查询聚合必须回到 DSL。

    image.png

    3、架构

    Elasticsearch 集群架构总体比较复杂,首先得深入了解 Elasticseach 背后实现的原理,包括集群原理、索引原理、数据写入过程、数据查询过程等;其次要有很多案例实战的机会,遇到很多挑战问题 ,逐一排除解决,增加自己的经验。

    对于开发工程师来说,满足日常需求开发无需掌握这些,但对于 Elasticsearch 技术负责人,就非常有必要了,面对各种应用需求,要能从架构思维去平衡,比如日志场景集群需求、大数据分析场景需求、应用系统复杂查询场景需求等,从实际情况设计集群架构以及资源分配等。

    4、运维

    Elasticsearch 本质是一个数据库,也需要有专门的 DBA 运维,只是更偏重应用层面,所以运维职责相对传统 DBA 没有那么严苛。对于集群层面必须掌握集群搭建,集群扩容、集群升级、集群安全、集群监控告警等;另外对于数据层面运维,必须掌握数据备份与还原、数据的生命周期管理,还有一些日常问题诊断等。

    5、源码

    Elasticsearch 本身是开源,阅读源码是个很好的学习手段,很多独特的特性官方操作文档并没有写出来,需要从源码中提炼,如集群节点之间的连接数是多少,但对于多数 Elasticsearch 从业者来说,却非必要。了解到国内主要是头部大厂需要深入源码定制化改造,更多的是集中在应用的便捷性改造,而非结构性的改造,Elastic 原厂公司有几百人的团队做产品研发,而国内多数公司就极少的人,所以从产量上来说,根本不是一个等级的。

    如果把 Elasticsearch 比喻为一件军事武器,对于士兵来说 ,熟练运用才是最重要的,至于改造应该是武器制造商的职责,一个士兵可以使用很多武器装备,用最佳的组合才能打赢一场战争,而不是去深入原理然后造轮子,容易本末倒置。

    6、算法

    算法应该算是数据产品本质的区别,关系型数据库索引算法主要是基于 B-Tree, Elasticserach 索引算法主要是倒排索引,算法的本质决定了它们的应用边界,擅长的应用领域。

    通常掌握一个新的数据产品时,个人的做法是看它的关键算法。早期做过一个地理位置搜索相关的项目,基于某个坐标搜索周边的坐标信息,开始的时候采用的是三角函数动态计算的方式,数据量大一点,扫描一张数据表要很久;后面接触到 Geohash 算法,按照算法将坐标编码,存储在数据库中,基于前缀匹配查询,性能高效几个数量级,感叹算法的伟大;再后面发现有专门的数据库产品集成了 Geohash 算法,使用起来就更简单了。

    Elasticsearch 集成很多算法,每种算法实现都有它的应用场景。

    拥抱 Elasticsearch 的方法

    1、官方文档

    Elasticsearch 早期出过一本参考手册《 Elastic 权威指南》,是一本很好的入门手册,从概念到实战都有涉及,缺点是版本针对的 2.0,过于陈旧,除去核心概念,其余的皆不适用,当前最新版本已经是 7.7 了,跨度太大,

    Elasticsearch 在跨度大的版本之间升级稍微比较麻烦,索引数据几乎是不兼容的,升级之后需要重建数据才可。

    Elasticsearch 当前最好的参考资料是官方文档,资料最全,同步发布版本,且同时可以参考多个版本。

    Elasticsearch 官方参考文档也是最乱的,什么资料都有,系统的看完之后感觉仍在此山中,有点类似一本字典,看完了字典,依然写不好作文;而且资料还是英文的,至此就阻挡了国内大部分程序进入。

    但想要学习 Elasticsearch,官方文档至少要看过几遍,便于迅速查询定位。

    image.png

    2、系统学习

    Elasticsearch 成名很早,国内也有很多视频课程,多数比较碎片,或是纸上谈兵,缺乏实战经验。Elasticsearch 有一些专门的书籍,建议购买阅读,国内深度一些的推荐《 Elasticsearch 源码解析与优化实战》,国外推荐《 Elasticsearch 实战》,而且看书还有助于培养系统思维。

    Elasticsearch 技术栈功能特性很多,系统学习要保持好的心态,持之以恒,需要很长时间,也需要参考很多资料。

    3、背后原理

    Elasticsearch 是站在巨人肩膀上产品,背后借鉴了很多设计思想,集成了很多算法,官方的参考文档在技术原理探讨这块并没有深入,仅仅点到为止。想要深入了解,必须得另辟蹊径。

    Elastic 官方的博客有很多优质的文章,很多人因为英文的缘故会忽视掉,里面有很多关键的实现原理,图文并茂,写得非常不错;另外国内一些云厂商由于提供了 Elasticsearch 云产品,需要深度定制开发,也会有一些深入原理系列的文章,可以去阅读参考,加深理解。对于已经有比较好的编程思维的人,也可以直接去下载官方源码,设置断点调试阅读。

    4、项目实战

    项目实战是非常有效的学习途径,考过驾照的朋友都深有体会,教练一上来就直接让你操练车,通过很多次的练习就掌握了。Elasticsearch 擅长的领域很多,总结一句话就是“非强事务 ACID 场景皆可适用”,所以可以做的事情也很多。

    日志领域的需求会让你对于数据写入量非常的关心,不断的调整优化策略,提高吞吐量,降低资源消耗;业务系统的需求会让你对数据一致性与时效性特别关心,从其它数据库同步到 Elasticsearch,关注数据同步的速度,关注数据的准确性,不断的调整你的技术方案与策略;大数据领域的需求会让你对于查询与聚合特别关注,海量的数据需要快速的检索,也需要快速的聚合结果。

    项目实战的过程,就是一个挖坑填坑的过程,实战场景多了,解决的问题多了,自然就掌握得很好了。

    之前笔者在前公司任职时,所有涉及到的 Elasticsearch 疑难杂症都会找我解决,有一些项目采用别的数据产品问题比较多,也来找我评估更换 Elasticsearch 是否合适,以及给出相关建议。笔者认为最好的学习方式是找到组织,找到经验丰富的大咖,持续交流学习,成长最快也最好。

    声明:本文由原文作者“李猛”授权转载,对未经许可擅自使用者,保留追究其法律责任的权利。


    image.png

    阿里云Elastic Stack】100%兼容开源ES,独有9大能力,提供免费 X-pack服务(单节点价值$6000)

    相关活动


    更多折扣活动,请访问阿里云 Elasticsearch 官网

    阿里云 Elasticsearch 商业通用版,1核2G ,SSD 20G首月免费
    阿里云 Logstash 2核4G首月免费


    image.png

    image.png

    ]]>
    高并发系统三大利器之缓存-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 引言

    随着互联网的高速发展,市面上也出现了越来越多的网站和app。我们判断一个软件是否好用,用户体验就是一个重要的衡量标准。比如说我们经常用的微信,打开一个页面要十几秒,发个语音要几分钟对方才能收到。相信这样的软件大家肯定是都不愿意用的。软件要做到用户体验好,响应速度快,缓存就是必不可少的一个神器。缓存又分进程内缓存和分布式缓存两种:分布式缓存如redismemcached等,还有本地(进程内)缓存如ehcacheGuavaCacheCaffeine等。

    缓存特征

    缓存作为一个数据数据模型对象,那么它有一些什么样的特征呢?下面我们分别来介绍下这些特征。

    命中率

    • 命中率=命中数/(命中数+没有命中数)当某个请求能够通过访问缓存而得到响应时,称为缓存命中。缓存命中率越高,缓存的利用率也就越高。

    最大空间

    • 缓存中可以容纳最大元素的数量。当缓存存放的数据超过最大空间时,就需要根据淘汰算法来淘汰部分数据存放新到达的数据。

    淘汰算法

    • 缓存的存储空间有限制,当缓存空间被用满时,如何保证在稳定服务的同时有效提升命中率?这就由缓存淘汰算法来处理,设计适合自身数据特征的淘汰算法能够有效提升缓存命中率。常见的淘汰算法有:
    FIFO(first in first out)
    • 先进先出。最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。适用于保证高频数据有效性场景,优先保障最新数据可用
    LFU(less frequently used)
    • 最少使用,无论是否过期,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount(命中次数)。适用于保证高频数据有效性场景
    LRU(least recently used)
    • 最近最少使用,无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。比较适用于热点数据场景,优先保证热点数据的有效性。

    进程缓存

    为什么需要引入本地缓存,本地缓存的应用场景有哪些?

    本地缓存的话是我们的应用和缓存都在同一个进程里面,获取缓存数据的时候纯内存操作,没有额外的网络开销,速度非常快。它适用于缓存一些应用中基本不会变化的数据,比如(国家、省份、城市等)。

    项目中一般如何适用、怎么样加载、怎么样更新?

    进程缓存的话,一般可以在应用启动的时候,把需要的数据加载到系统中。更新缓存的话可以采取定时更新(实时性不高)。具体实现的话就是在应用中起一个定时任务(ScheduledExecutorServiceTimerTask等),让它每隔多久去加载变更(数据变更之后可以修改数据库最后修改的时间,每次查询变更数据的时候都可以根据这个最后变更时间加上半小时大于当前时间的数据)的数据重新到缓存里面来。如果觉得这个比较麻烦的话,还可以直接全部全量更新(就跟项目启动加载数据一样)。这种方式的话,对数据更新可能会有点延迟。可能这台机器看到的是更新后的数据,那台机器看到的数据还是老的(机器发布时间可能不一样)。所以这种方式比较适用于对数据实时性要求不高的数据。如果对实时性有要求的话可以通过广播订阅mq消息。如果有数据更新mq会把更新数据推送到每一台机器,这种方式的话实时性会比前一种定时更新的方法会好。但是实现起来会比较复杂。
    在这里插入图片描述

    本地缓存有哪些实现方式?

    常见本地缓存有以下几种实现方式:
    图片来源于https://juejin.im/post/6844903665845665805
    从上述表格我们看出性能最佳的是Caffeine。关于这个本地缓存的话我还是强烈推荐的,里面提供了丰富的api,以及各种各样的淘汰算法。如需了解更加详细的话可以看下以前写的这个篇文章《本地缓存性能之王Caffeine》

    本地缓存缺点

    • 本地缓存与业务系统耦合再一起,应用之间无法直接共享缓存的内容。需要每个应用节点单独的维护自己的缓存。每个节点都需要一份一样的缓存,对服务器内存造成一种浪费。本地缓存机器重启、或者宕机都会丢失。

    分布式缓存

    • 分布式缓存是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。常见的分布式缓存有redisMemCache等。

    分布式缓存的应用

    在高并发的环境下,比如春节抢票大战,一到放票的时间节点,分分钟大量用户以及黄牛的各种抢票软件流量进入12306,这时候如果每个用户的访问都去数据库实时查询票的库存,大量读的请求涌入到数据库,瞬间Db就会被打爆,cpu直接上升100%,服务马上就要宕机或者假死。即使进行了分库分表也是无法避免的。为了减轻db的压力以及提高系统的响应速度。一般都会在数据库前面加上一层缓存,甚至可能还会有多级缓存。

    缓存常见问题

    缓存雪崩

    指大量缓存同一时间段集体失效,或者缓存整体不能提供服务,导致大量的请求全部到达数据库
    对数据CPU和内存造成巨大压力,严重的会造成数据库宕机。因此而形成的一系列连锁反应造成整个系统奔溃。
    解决这个问题可以从以下方面入手:

    • 保证缓存的高可用。使用redis的集群模式,即使个别redis节点下线,缓存还是可以用。一般稍微大点的公司还可能会在多个机房部署Redis。
      这样即使某个机房突然停电,或者光纤又被挖断了,这时候缓存还是可以使用。
    • 使用多级缓存。不同级别缓存时间过时时间不一样,即使某个级别缓存过期了,还有其他缓存级别
      兜底。比如我们Redis缓存过期了,我们还有本地缓存。这样的话即使没有命中redis,有可能会命中本地缓存。
    • 缓存永不过期。Redis中保存的key永久不失效,这样的话就不会出现大量缓存同时失效的问题,但是这种做法会浪费更多的存储空间,一般应该也不会推荐这种做法。
    • 使用随机过期时间。为每一个key都合理的设计一个过期时间,这样可以避免大量的key再同一时刻集体失效。
    • 异步重建缓存。这样的话需要维护每个key的过期时间,定时去轮询这些key的过期时间。例如一个keyvalue设置的过期时间是30min,那我们可以为这个key设置它自己的一个过期时间为20min。所以当这个key到了20min的时候我们就可以重新去构建这个key的缓存,同时也更新这个key的一个过期时间。
    缓存穿透

    指查询一个不存在的数据,每次通过接口或者去查询数据库都查不到这个数据,比如黑客的恶意攻击,比如知道一个订单号后,然后就伪造一些不存在的订单号,然后并发来请求你这个订单详情。这些订单号在缓存中都查询不到,然后会导致把这些查询请求全部打到数据库或者SOA接口。这样的话就会导致数据库宕机或者你的服务大量超时。
    这种查询不存在的数据就是缓存击穿。
    解决这个问题可以从以下方面入手:

    • 缓存空值,对于这些不存在的请求,仍然给它缓存一个空的结果,这种方式简单粗暴,但是如果后续这个请求有新值了需要把原来缓存的空值删除掉(所以一般过期时间可以稍微设置的比较短)。
    • 通过布隆过滤器。查询缓存之前先去布隆过滤器查询下这个数据是否存在。如果数据不存在,然后直接返回空。这样的话也会减少底层系统的查询压力。
    • 缓存没有直接返回。 这种方式的话要根据自己的实际业务来进行选择。比如固定的数据,一些省份信息或者城市信息,可以全部缓存起来。这样的话数据有变化的情况,缓存也需要跟着变化。实现起来可能比较复杂。
    缓存击穿

    是指缓存里面的一个热点key(拼多多的五菱宏光神车的秒杀)在某个时间点过期。针对于这一个key有大量并发请求过来然后都会同时去数据库请求数据,瞬间对数据库造成巨大的压力。
    这个的话可以用缓存雪崩的几种解决方法来避免:

    • 缓存永不过期。Redis中保存的key永久不失效,这样的话就不会出现大量缓存同时失效的问题,但是这种做法会浪费更多的存储空间,一般应该也不会推荐这种做法。
    • 异步重建缓存。这样的话需要维护每个key的过期时间,定时去轮询这些key的过期时间。例如一个keyvalue设置的过期时间是30min,那我们可以为这个key设置它自己的一个过期时间为20min。所以当这个key到了20min的时候我们就可以重新去构建这个key的缓存,同时也更新这个key的一个过期时间。
    • 互斥锁重建缓存。这种情况的话只能针对于同一个key的情况下,比如你有100个并发请求都要来取A的缓存,这时候我们可以借助redis分布式锁来构建缓存,让只有一个请求可以去查询DB其他99个(没有获取到锁)都在外面等着,等A查询到数据并且把缓存构建好之后其他99个请求都只需要从缓存取就好了。原理就跟我们javaDCL(double checked locking)思想有点类似。
      在这里插入图片描述

    缓存更新

    我们一般的缓存更新主要有以下几种更新策略:

    • 先更新缓存,再更新数据库
    • 先更新数据库,再更新缓存
    • 先删除缓存,再更新数据库
    • 先更新数据源库,再删除缓存
      至于选择哪种更新策略的话,没有绝对的选择,可以根据自己的业务情况来选择适合自己的不过一般推荐的话是选择 先更新数据源库,再删除缓存。关于这几种更新的介绍可以推荐大家看下博客园大佬孤独烟写的《分布式之数据库和缓存双写一致性方案解析》这一篇文章,看完文章评论也可以去看看,评论跟内容一样精彩。

    总结

    如果想要真正的设计好一个缓存,我们还是必须要掌握很多的知识,对于不同场景,缓存有各自不同的用法。比如实际工作中我们对于订单详情的一个缓存。我们可能会根据订单的状态来来构建缓存。我们就以机票订单为例,已出行、或者已经取消的订单我们基本上是不会去管的(订单状态已经终止了),这种的话数据基本也不会变了,所以对于这种订单我们设置的过期时间是不是就可以久一点,比如7天或者30天。对于未出行即将起飞的订单,这时候顾客是不是就会频繁的去刷新订单看看,看看有没有晚点什么的,或者登机口是在哪。对于这种实时性要求比较高的订单我们过期时间还是要设置的比较短的,如果是需要更改订单的状态查询的时候可以直接不走缓存,直接查询master库。毕竟这种更改订单状态的操作还是比较有限的。大多数情况都是用来展示的。展示的话是可以允许实时性要求没那么高。总的来说需要开具体的业务,没有通用的方案。看你的业务需求的容忍度,毕竟脱离了业务来谈技术都是耍流氓,是业务驱动技术。

    结束

    • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
    • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
    • 感谢您的阅读,十分欢迎并感谢您的关注。

    站在巨人的肩膀上摘苹果:
    https://juejin.im/post/6844903665845665805
    https://tech.meituan.com/2017/03/17/cache-about.html
    https://www.cnblogs.com/rjzheng/p/9041659.html#!comments

    ]]>
    【其他】9月1日云监控CDN产品告警指标数据优化通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【云监控CDN产品告警指标数据优化通知】

    为提升用户体验,云监控CDN产品监控据将全面替换为日志数据,数据最小颗粒度为1分钟颗粒,将在9月1日进行数据切换。

    影响

    1,用户概览、域名监控图表里,网络带宽数据,当最小时间颗粒度为1分钟时,可能出现周期内同一时间的带宽峰值比5分钟颗粒度的峰值更小;

    2,报警规则里,若“带宽峰值”告警设置周期为1分钟,可能会比设置成5分钟周期产生更多的告警;

    3,1分钟数据为阿里云CDN的实时日志数据,与客户的真实业务一致,5分钟的数据更接近带客户计费带宽数据,在1分钟颗粒度下的产生更多的带宽告警为正常现象;

    ]]>
    【升级】8月13日Afilias注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云域名】【Afilias注册局维护通知】

    维护时间:北京时间 2020年8月13日 01:30 - 8月14日 04:35

    维护内容:接到注册局的通知,注册局中国站将于上述时间对后台系统进行维护升级。

    维护影响:届时 .Red/.Kim/.Pro/.Asia/.Info/.Mobi/.ORG域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月20日消息队列AMQP升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列AMQP】【升级通知】

    升级窗口:北京时间2020年8月20日(周四) 14:30 - 18:30

    升级内容:华北1(青岛)、华北2(北京)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、香港等地域的消息队列AMQP服务升级。

    升级影响:升级期间消息队列AMQP相关服务及控制台访问可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过5分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【漏洞预警】Windows NetLogon权限提升漏洞(CVE-2020-1472) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月12日,阿里云应急响应中心监测到微软发布补丁修复了NetLogon权限提升漏洞(CVE-2020-1472),CVSS评分10,官方评级严重。目前微软官方已提供相应的月度安全补丁以修复该漏洞。


    漏洞描述

    微软官方于8月12日发布安全更新,其中修复了一个修复了NetLogon权限提升漏洞(CVE-2020-1472),攻击者通过NetLogon(MS-NRPC),建立与域控间易受攻击的安全通道时,可利用此漏洞获取域管访问权限。成功利用此漏洞的攻击者可以在该网络中的设备上运行相应代码或者程序。同时,微软8月补丁中还涉及其他多个高危漏洞,阿里云应急响应中心提醒 Windows 用户尽快安装补丁阻止漏洞攻击。


    漏洞评级

    CVE-2020-1472 严重


    影响版本

    Windows Server 2008 R2 for x64-based Systems Service Pack 1

    Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation)

    Windows Server 2012

    Windows Server 2012 (Server Core installation)

    Windows Server 2012 R2

    Windows Server 2012 R2 (Server Core installation)

    Windows Server 2016

    Windows Server 2016 (Server Core installation)

    Windows Server 2019

    Windows Server 2019 (Server Core installation)

    Windows Server, version 1903 (Server Core installation)

    Windows Server, version 1909 (Server Core installation)

    Windows Server, version 2004 (Server Core installation)


    安全建议

    1、前往微软官方下载相应补丁进行更新 https://portal.msrc.microsoft.com/zh-CN/security-guidance/advisory/CVE-2020-1472

    2、阿里云云安全中心Windows系统漏洞模块已支持对该漏洞补丁一键检测和修复,详情登陆云安全中心

    Windows Server 2008 (官方已停止免费补丁维护,需购买微软ESU服务,建议放弃使用:https://www.microsoft.com/en-us/windows-server/extended-security-updates

    Windows Server 2012 补丁:KB4571736

    Windows Server 2012 R2 补丁:KB4571703

    Windows Server 2016 补丁:KB4571694

    Windows Server 2019 补丁:KB4565349



    相关链接

    https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1472



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.08.12

    ]]>
    【漏洞预警】Apache Struts远程代码执行漏洞(S2-059、CVE-2019-0230) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月13日,阿里云应急响应中心监测到Apache Struts 官方发布安全公告,披露 S2-059 Struts 远程代码执行漏洞。


    漏洞描述

    Apache Struts2框架是一个用于开发Java EE网络应用程序的Web框架。Apache Struts于2020年8月13日披露 S2-059 Struts 远程代码执行漏洞(CVE-2019-0230),在使用某些tag等情况下可能存在OGNL表达式注入漏洞,从而造成远程代码执行,风险极大。阿里云应急响应中心提醒Apache Struts用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    Apache Struts 2.0.0 - 2.5.20


    安全版本

    Apache Struts >= 2.5.22


    安全建议

    将Apache Struts框架升级至最新版本。


    相关链接

    https://cwiki.apache.org/confluence/display/WW/S2-059



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.08.13

    ]]>
    【漏洞预警】Apache Shiro < 1.6.0 权限绕过漏洞(CVE-2020-13933) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月17日,阿里云应急响应中心监测到Apache Shiro官方发布安全更新,修复了一个最新权限绕过漏洞。攻击者利用该漏洞可以绕过验证访问到后台功能,风险较高。


    漏洞描述

    Apache Shiro是一个应用广泛的权限管理的用户认证与授权框架。近日,shiro被爆出Apache Shiro 身份验证绕过漏洞 (CVE-2020-11989),攻击者可以使用包含payload的恶意请求绕过Shiro的身份认证,漏洞于1.5.3修复。实际上,这个修复并不完全,由于shiro在处理url时与spring仍然存在差异,shiro最新版仍然存在身份校验绕过漏洞。2020年8月17日,Apache Shiro发布1.6.0版本修复该漏洞绕过。阿里云应急响应中心提醒Apache Shiro用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    Apache Shiro < 1.6.0


    安全版本

    Apache Shiro >= 1.6.0


    相关链接

    https://lists.apache.org/thread.html/r539f87706094e79c5da0826030384373f0041068936912876856835f%40%3Cdev.shiro.apache.org%3E

    https://shiro.apache.org/news.html

    阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.08.18

    ]]>
    基于GAN的个性化短标题生成在1688平台的实践应用-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    一、背景介绍

    我们团队从去年的工作中,积累了风格化文案生成算法的内容生成方案。之后,我们创新的提出使用Generative Adversarial Network的生成技术,结合用户特征进行个性化短标题的生成,同时提高稀疏数据集下模型的鲁棒性。目前成果已被DLP-KDD2020会议录用,论文题目是“Selling Products by Machine: a User-Sensitive Adversarial Training method for Short Title Generation in Mobile E-Commerce”。

    过去的相关研究主要从商品的内容出发,而缺少对用户行为的挖掘,从而忽视了买家的需求。我们提出了“个性化指针生成对抗网络(PPGAN)”,生成用户感知的个性化短标题。此外,在数据集上,电商用户行为数据中用户的低点击率使得我们的数据集非常稀疏,本文模型引入一种无监督的信息理论策略,能够从用户未点击数据中识别出高质量的短标题训练模型。

    e13780340421439e8d58cdf48b2a80d8.png

    二、模型结构

    55b4d9b383ac485b85968bf3286aa620.png

    PPGAN模型网络整体包含三个模块,1.电商词嵌入,2.个性化短标题生成器G,3.两种不同的判别器D:真假判别器和CTR判别器。这三个模块共同组成我们的个性化短标题生成模型框架。

    1.电商词嵌入

    我们使用AliNLP的电商NER工具对商品标题和用户特征进行识别,提取“颜色”、“风格”、“品类”等实体类型特征。例如,在商品标题“包邮Nike品牌的红色运动裤”中,“包邮”标记为“市场服务”,“Nike”标记为“品牌”,“红色”标记为“颜色”,“运动裤”标记为“品类”。对于每个单词,我们将其单词向量和NER向量拼接,共同作为模型的词向量输入。

    对于商品标题序列,每个单词被表示成:

    023496e12070461f877deda341105795.png

    对于用户特征序列,每个单词被表示成:

    0405ae1ca74d42ac9c6986a0d4cee83a.png

    通过加入单词的NER信息,模型更能够学习到每个单词的重要性,将重要的单词保留在短标题中。

    2. 个性化短标题生成器

    对于短标题生成器来说,输入信息为商品标题和用户特征,通过one-step Pointer Networks模型从原标题中抽取式的生成短标题,与传统的多步指针网络相比,一步指针网络在解码阶段一步解码,避免了多步解码的重复性抽取问题。

    用户特征的构建:我们基于用户在平台的点击记录运用统计方法计算用户特征,具体来说,对于用户点击过的商品item_t,收集点击itemt之前点击过的10个商品数据,通过词频统计取TOP10的单词作为用户特征U=(u_1, u_2, ... , u_10)。

    如图所示,在用户特征构建后,将用户特征向量序列输入GRU网络中,计算得到用户表征。

    83ffa4dd0ad141a19904cadc6323d30f.png

    为了融合用户和被点击的商品的特征,我们将用户表征与商品词向量进行拼接,再输入GRU编码器进行编码。

    c05239f96caf4f6a8465cb964a91829e.png

    c13c64a9f081428999a5987b8c43e92b.png

    在解码部分,通过基于注意力机制的一步解码和softmax层,输出在原输入序列上的概率分布,取概率TopK的单词作为生成的短标题。

    dcc5023f422b40dab8ae65a9f9c48f04.png

    b53d7d0c6d394f39994ae135a3a89373.png

    3. 判别器

    判别器D是一个以生成器G的输出分布Pg和真实短标题分布Pr为输入的二元分类器,用来判别是商家手写真实短标题还是机器生成的伪短标题。我们将真实短标题的输入分布定义如下,其中T为原始长标题,S=(s_1, s_2, ... , s_m)为真实短标题,m是短标题的长度。

    e93a5d39f8144fb4989ef5210abf0dd8.png

    同时,为了让判别器不那么容易的判别出真伪短标题,我们给真伪短标题的分布加入服从高斯分布的噪音,使训练过程更加稳定。

    bd2af0fcf434432fbaa56436ec6a3eee.png

    将这两个标题分布与商品词向量点乘得到“短标题”的向量表示,在拼接用户特征后分别输入判别器D进行判别。
    判别器D网络采用常用的卷积神经网络,包括真假判别器和CTR判别器两种。真假判别器用来使生成器生成的短标题更接近用户点击的真实短标题;此外,考虑到数据集的稀疏性,用户点击样本量相对于未点击样本量较少,因此我们希望引入CTR判别器,从大量的用户未点击样本中识别高质量的短标题训练模型。

    4. 对抗训练损失与模型训练过程

    生成器G尽力为目标用户生成接近真实的短标题分布,判别器D尽力最大化真伪短标题分布之间的距离,从而做出正确判别。算法流程如下图所示:

    998d9f918e754f3cbe5645e6355ac88d.png

    • 首先使用大量长短标题pair对预训练生成器G,快速更新我们的生成器。由于没有足够的用户点击数据覆盖所用商品,我们在预训练阶段仅使用商品标题特征,而不考虑用户特征。我们使用L2损失函数作为预训练生成器G时的损失:

      1a8f76cec38947cea569da8350feedbb.png

    • 正式训练阶段1,输入数据为用户点击的商品数据[T+, U+, S+],生成器G和真假判别器D的损失函数如下:

      39234649d2374de79c82ae3eea6183af.png

      9b4eef3e422941bc8ed20fab9a47a9ea.png

    • 正式训练阶段2,输入数据为用户未点击的商品数据[T-, U-]。事实上,用户未点击的短标题并不仅仅由于短标题质量不好,也可能是用户对商品本身不感兴趣。因此,我们考虑通过对抗的方式识别出用户未点击但质量好的短标题。我们借鉴了现有的工作,最大化正负例之间的信息边界,在判别器损失中加入正负两个类别之间的加性条件熵M_D(x)。CTR判别器的损失函数如下:

      748af142d8264e2a865f236c5e6f16a9.png

      a080749b04fa49cf9130f888cc3cf9d0.png

    三、实验结果与线上效果

    1. 样本数据和训练

    我们的训练样本来自1688平台商家手写的短标题以及现有短标题场景中用户的行为数据,可以将数据集表示成,其中O为商品原始长标题,S表示手写短标题,U为用户特征序列,L为用户对商品的标签(1表示点击,0表示未点击),样例如下图所示。我们的用户点击样本量为64万,用户未点击样本量为695万,因此数据集较为稀疏。此外,数据样本上,经过了一定的数据清理工作,因为本身训练样本数据的质量会严重影响最终生成短标题的质量,我们的处理主要包括脏语料的剔除、异常字符的过滤、原始标题长度限制等,同时利用AliNLP进行用户和商品特征的命名实体识别工作。

    数据样例如下:

    c4ae05ee62294fefb2b14359cd4a3f71.png

    2. 个性化短标题生成样例

    与对比方法相比,我们的模型PPGAN能够根据不同的用户特征,从原始长标题中抽取特定用户感兴趣的信息生成个性化的短标题。表中短标题的生成长度设定为5,根据不同的产品和场景需求,我们能够生成不同长度的个性化短标题,从而让生成的短标题更贴合业务需求。

    PPGAN实验生成样例如下图所示:

    48be636c155844d784ec28dbff491de9.png

    3. 1688落地效果

    目前个性化短标题已落地到1688平台的多个场景中,包括伙拼、天天特卖、厂货集采等。我们在榜单的“发现好货”进行AB测试,结果上实时个性化短标题比截断长标题点击率绝对值高出2.3个点左右,比统计短标题高出1.7个点左右。并且在2019年的920大促中,全量上线到大促会场中,承接住1100+QPS的调用量,为1688导购场景全面赋能,目前已沉淀为场景侧技术工具,供运营使用。

    线上效果如下:

    55c86be6b8374efdbd5600cc85d4a5e3.png

    四、参考文献

    [1] Martin Arjovsky, Soumith Chintala, and Leon Bottou. 2017. Wasserstein Generative Adversarial Networks. In ICML. 214–223.
    [2] Oriol Vinyals, Meire Fortunato, and Navdeep Jaitly. 2015. Pointer networks. In NIPS. 2692–2700.
    [3] Tao Zhang, Jin Zhang, Chengfu Huo, and Weijun Ren. 2019. Automatic Generation of Pattern-controlled Product Description in E-commerce. In WWW. 2355–2365.
    [4] Jiatao Gu, Zhengdong Lu, Hang Li, and Victor O K Li. 2016. Incorporating copying mechanism in sequence-to-sequence learning. In ACL. 1631–1640.
    [5] Yue Deng, Yilin Shen, and Hongxia Jin. 2017. Disguise Adversarial Networks for Click-through Rate Prediction. In IJCAI. 1589–1595.

    386ecdcbf98a403db932936d3adcd472.png]]> 基于日志服务数据加工分析Java异常日志-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1. 本文要点(regex_findall()、e_regex()、e_output()专题):

    • 正则解析复杂异常日志(一次性解析不出全部格式异常日志怎么办?)
    • 根据不同产品的Exception做数据清洗以及多目标分发
    • 对不同的产品的异常日志做数据分析

    2. 背景

    业务背景:
    采集并脱敏了整个5月份的项目异常日志,准备使用日志服务数据加工做数据清洗以及分析。本案例是基于使用阿里云相关产品(OSS,RDS,SLS等)的SDK展开自身业务。
    业务需求:
    需要对异常日志做解析,将原始日志中时间、错误码、错误信息、状态码、产品信息、请求方法、出错行号提取出来。然后根据提取出来的不同产品信息做多目标分发处理。对清洗后的数据做异常日志数据分析。

    3. 案例总体流程

    本案例中的所有异常日志全部是通过使用日志服务SDK上传到cloud_product_error_log的logstore中,然后再经过数据加工清洗数据后投递到各自产品的logstore中,最后针对各个产品的异常日志做分析。具体流程如下:
    image.png

    4. 原始日志样例

    image.png

    原始日志中只有错误日志信息message,需要将其message中的一些重要信息解析出来。

    5. 加工需求

    (1) 将message错误信息中的时间、错误码、状态码、产品信息、错误码信息、请求方法、出错行号提取出来
    (2) 根据提取出的产品信息对清洗后的数据进行多目标分发

    具体需求流程如下图所示:
    image.png

    6. 对异常日志数据进行解析和投递

    6.1 加工逻辑分析

    下图展示的是如何从原始异常日志经过数据加工得到解析后的日志信息(原始日志形式,预期日志形式),然后投递到不同logstore中,最后对不同产品的异常日志做数据分析。本案例中只对SLS异常日志和OSS异常日志做加工分析。
    image.png

    6.2 加工操作

    经过以上对异常日志的分析以及加工流程的剖析,此时我们可以对异常日志加工进行实操(对于不熟悉如何使用日志服务数据加工的同学可以参考基于日志服务(SLS)实现电商数据加工与分析)。以下是具体的加工语法:

    e_switch(
        regex_match(v("message"), r"LogException"),e_compose(e_regex("message", "(?P<data_time>S+sS+)s(?P<error_code>[a-zA-Z]+)s(?P<status>[0-9]+)scom.aliyun.openservices.log.exception.(?P<product_exception>[a-zA-Z]+):(?P<error_message>[a-zA-Z0-9:,-s]+).(s+S+sS+){5}s+S+scom.aliyun.openservices.log.Client.(?P<method>[a-zA-Z]+)S+s+S+stransformEvent.main(transformEvent.java:(?P<error_line>[0-9]+))"),e_drop_fields("message"),e_output("sls-error")),
        regex_match(v("message"), r"OSSException"),e_compose(e_regex("message", "(?P<data_time>S+sS+)scom.aliyun.oss.(?P<product_exception>[a-zA-Z]+):(?P<error_message>[a-zA-Z0-9,s]+).n[ErrorCode]:s(?P<error_code>[a-zA-Z]+)n[RequestId]:s(?P<request_id>[a-zA-Z0-9]+)n[HostId]:s(?P<host_id>[a-zA-Z-.]+)nS+nS+(sS+){3}ns+S+s+(.+)(s+S+){24}scom.aliyun.oss.OSSClient.(?P<method>[a-zA-Z]+)S+s+S+stransformEvent.main(transformEvent.java:(?P<error_line>[0-9]+))"),e_drop_fields("message"),e_output("oss-error"))
    )

    点击预览效果如下所示:
    image.png

    上图表面加工语法能够正常解析异常日志,接下来需要进行保存加工任务操作。具体加工配置如下图:
    image.png

    注意:
    以上语法中的e_output中的name参数需要与加工配置规则中的目标名称一一对应。更多用法请参考e_output语法注意事项

    6.3 语法详解

    其中涉及到的正则语法分组相关知识请移步正则表达式-分组
    (1) 首先使用regex_match函数匹配出此条日志中是否有LogException如果匹配上则走解析sls异常日志的规则分支,如果匹配上OSSException则走解析OSS异常日志的规则分支。
    (2) 其次使用e_regex正则解析函数解析相应的异常日志。
    (3) 最后删除原字段message信息,并投递到相应产品的logstore中。

    其中本案例中正则解析日志详解是以解析SLS异常日志为例,具体如下
    image.png

    7. 数据加工诊断

    当我们在解析日志的时候,时常会遇到一次性解析不出全部格式异常日志的情况。并且在加工预览页面只会预览一定量的数据,因此在加工预览页面不一定会提示出该正则解析不出的日志的情况。
    综上,我们可以通过查看数据加工概览(具体如何查看请参考加工概览)的方式查到具体是那一条日志没有解析出来,然后再调整我们的正则表达式。解析失败样例,如下图所示:
    image.png

    具体异常详情的logging如下:

    {
        "message": "transform_regex: field value "2020-05-04 08:45:07 ServerBusy 505 com.aliyun.openservices.log.exception.LogException:The server is busy, please try again later.n    at com.aliyun.openservices.log.Client.ErrorCheck(Client.java:2161)n    at com.aliyun.openservices.log.Client.SendData(Client.java:2312)n    at com.aliyun.openservices.log.Client.CreateConsumerGroup(Client.java:2190)n    at com.aliyun.openservices.log.Client.SendData(Client.java:2265)n    at com.aliyun.openservices.log.Client.GetCursor(Client.java:1123)n    at com.aliyun.openservices.log.Client.CreateConsumerGroup(Client.java:1100)n    at transformEvent.main(transformEvent.java:1950)" cannot extract value with config "(?P<data_time>\S+\s\S+)\s(?P<error_code>[a-zA-Z]+)\s(?P<status>[0-9]+)\scom\.aliyun\.openservices\.log\.exception\.(?P<__topic__>[a-zA-Z]+)\:(?P<error_message>[a-zA-Z0-9\s:-]+)\.(\s+\S+\s\S+){5}\s+\S+\scom\.aliyun\.openservices\.log\.Client\.(?P<method>[a-zA-Z]+)\S+\s+\S+\stransformEvent\.main\(transformEvent\.java\:(?P<error_line>[0-9]+)\)"",
        "processName": "MainProcess",
        "process": "1",
        "thread": "139873923098368",
        "levelname": "WARNING",
        "module": "regex_impl",
        "threadName": "ThreadPoolExecutor-0_1",
        "funcName": "__call__"
    }

    从以上图例和具体信息可以看到,如果正则解析失败会报WARNING级别日志。此时的加工处理规则是跳过加工失败的日志继续往下加工,此过程中并不会影响到整个加工任务的执行(只有ERROR级别的会影响到整个加工任务的消费)。
    接下来主要关注message中的日志信息,看具体是那条日志或者那一类异常日志导致现有的正则规则加工失败。
    其message中的解析失败的日志和正则(logging中的正则表达式需要自己去掉多加的转义反斜杠)。

    """解析失败日志"""
    2020-05-04 08:45:07 ServerBusy 505 com.aliyun.openservices.log.exception.LogException:The server is busy, please try again later.n    at com.aliyun.openservices.log.Client.ErrorCheck(Client.java:2161)n    at com.aliyun.openservices.log.Client.SendData(Client.java:2312)n    at com.aliyun.openservices.log.Client.CreateConsumerGroup(Client.java:2190)n    at com.aliyun.openservices.log.Client.SendData(Client.java:2265)n    at com.aliyun.openservices.log.Client.GetCursor(Client.java:1123)n    at com.aliyun.openservices.log.Client.CreateConsumerGroup(Client.java:1100)n    at transformEvent.main(transformEvent.java:1950)
    """正则表达式"""
    (?P<data_time>S+sS+)s(?P<error_code>[a-zA-Z]+)s(?P<status>[0-9]+)scom.aliyun.openservices.log.exception.(?P<product_exception>[a-zA-Z]+):(?P<error_message>[a-zA-Z0-9s:-]+).(s+S+sS+){5}s+S+scom.aliyun.openservices.log.Client.(?P<method>[a-zA-Z]+)S+s+S+stransformEvent.main(transformEvent.java:(?P<error_line>[0-9]+))

    经过一步一步的查找验证,得出在解析error_message中缺少了对有逗号情景的解析,因此此地方正则需要改善,具体改善如下:

    """解析失败error_message正则"""
    (?P<error_message>[a-zA-Z0-9s:-]+)
    """完善后解析error_message正则"""
    (?P<error_message>[a-zA-Z0-9:,-s]+)

    综上,经过我们不断的完善正则表达式,目前是能够成功解析各个不同类型的异常日志。接下来我们将进入数据分析阶段。

    8. 异常日志数据分析

    下图是通过sql查询所展示数据仪表大盘:
    image.png

    基于加工后的异常日志(以SLS错误日志为例分析),在这里我们的需求有以下几方面:
    (1) 各个方法异常统计
    (2) 各个方法异常占比统计图
    (3) PutLogs错误信息
    (4) 各个方法调用报错每日level气泡图
    (5) 各个ErrorCode统计分析
    (6) 各个方法报错时间轴

    8.1 各个方法异常统计和各个方法异常占比统计图

    为了方便查看那个方法出现错误次数最高和帮助我们定位业务调用的具体方法,因此我们先对各个方法出现的异常情况做一个统计。

    * | SELECT COUNT(method) as m_ct, method GROUP BY method

    这条语句表示的是统计每个调用方法出现异常的数量。
    下图表示的是在查询框里输入以上sql分析语句之后进入统计图表,然后点击条形图进行配置,method为X轴,m_ct为Y轴。
    image.png

    通过上图分析可知总体情况,PutLogs调用出现异常的次数最多,其中GetCursor和DeleteConsumerGroup是出现异常的次数是最少的。
    此外,为了方便我们查看各个方法异常占比情况,我们可以基于以上的SQL做一个占比统计图,点击漏斗图,然后以method为分组列,m_ct为数值列具体操作如下。
    image.png

    8.2 PutLogs错误信息

    为了方便我们查看哪一类的错误信息是占比最大,帮助我们分析:
    (1) 需要自身业务需要改进的
    (2) 需要SLS帮忙调整的(如调整quota)
    (3) 需要深入项目中具体排查错误的(具体的error line)

    * | SELECT error_message,COUNT(error_message) as ct_msg, method WHERE method LIKE 'PutLogs' GROUP BY error_message,method

    点击矩形树图,然后以error_message为分类和ct_msg为数值列。
    image.png

    从上图中我们可知:
    (1) consumer group already exist、consumer group not exist、Logs must be less than or equal to 3 MB and 4096 entries等这些都是属于需要在项目中具体排查的错误
    (2) Read/ Write quota is exceeded这些是需要SLS调整quota的
    (3) 还有一些是需要调整自身业务的比如logstore/ project not exist看是否是因为一些业务原因而导致这些project被取消等等。

    8.3 各个方法调用报错每日level气泡图

    为了方面我们查看每一天的各个调用方法出错频次,我们需要有一个直观的图来查看每一天的调用出错程度,这样我们可以更加直观的去看某一天的具体调用出错细节。

    * | select date_format(data_time, '%Y-%m-%d') as day,COUNT(method) as count_method, case 
    when method = 'PullLogs' and COUNT(method) > 21800 then 3 when method = 'PullLogs' and COUNT(method)>21400 then 2 when method = 'PullLogs' and COUNT(method)>21100 then 1 
    when method = 'CreateConsumerGroup' and COUNT(method) > 21900 then 3
    when method = 'CreateConsumerGroup' and COUNT(method) > 21700 then 2 when method = 'CreateConsumerGroup' and COUNT(method) > 21550 then 1 
    when method = 'PutLogs' and COUNT(method)>43900 then 3 when method = 'PutLogs' and COUNT(method)>43300 then 2 when method = 'PutLogs' and COUNT(method)>42900 then 1 
    when method = 'DeleteConsumerGroup' and COUNT(method)>7440 then 3 when method = 'DeleteConsumerGroup' and COUNT(method)>7330 then 2 when method = 'DeleteConsumerGroup' and COUNT(method)>7320 then 1 
    when method = 'GetCursor' and COUNT(method)>7350 then 3 when method = 'GetCursor' and COUNT(method)>7200 then 2 when method = 'GetCursor' and COUNT(method)>7150 then 1
    else 0 end as level,  method group by day, method ORDER BY day asc

    其中如果一天PullLogs出错次数大于21800则属于level3级别的,大于21400则属于level2级别,大于21100则属于level1,否则属于level0,然后再跟据每一天每个方法做聚合操作。
    点击气泡图,其中以day为X轴,method为Y轴。

    从上图中可以明显的知道每一天的每个方法的出现异常的level值,针对某一天level值高的可以对其做具体的错误查看到底是什么原因引起的。

    8.4 各个ErrorCode统计分析

    统计各个error_code出现的错误次数,能够更好的帮助我们查看哪些是因为ServerBusy,哪些是因为ParameterInvalid等不同的原因引起的异常,从而更好的定位异常原因。

    * | SELECT error_code,COUNT(error_code) as count_code GROUP BY error_code

    点击折线图,以error_code为X轴,以count_code为Y轴
    image.png

    基于上图可知WriteQuotaExceed出现异常次数最多,可以查查那个project下的logstore被限制,然后咨询相关的产品技术支持。

    8.5 各个方法报错时间轴

    设置报错时间轴,可以实时的查看调用接口异常信息。

    * | SELECT date_format(data_time, '%Y-%m-%d %H:%m:%s') as date_time,status,product_exception,error_line, error_message,method ORDER BY date_time desc

    image.png

    基于上图所示,我们可以对重要的接口出现异常做标注高亮显示,以便我们能够快速识别出重要接口异常情况。

    9. 总结

    对于其他的产品的异常日志,我们也可以使用同样的方法加工清洗投递,然后SQL分析自己所关心的业务指标。

    ]]>
    sharding-sphere中的xa事务-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Apache ShardingSphere 默认的 XA 事务管理器为 Atomikos。

    j2ee对分布式事务定义了标准的规范,分别是JTA和JTS,JTA(Java Transaction API)是根据XA规范制定的java版本的接口规范,Atomikos就是jta的一种实现。JTA中约定了几种主要的程序角色:客户端、应用服务器、事务管理器、资源管理器。
    JTA和JTS有什么关系呢?事务管理器要和资源管理器要进行事务上下文传播的交互,其中应用服务器和事务管理器之间也有传播事务上下文的交互,JTS就是定义各个程序角色之间如何传递事务上下文的规范。

    JTA从框架的角度来约定实现者需要实现的接口,JTS约定了具体程序角色应该怎样去进行交互。

    XA是X/Open CAE Specification (Distributed Transaction Processing)模型中定义的TM(Transaction Manager)与RM(Resource Manager)之间进行通信的接口。

    在XA规范中,数据库充当RM角色,应用需要充当TM的角色,即生成全局的txId,调用XAResource接口,把多个本地事务协调为全局统一的分布式事务。

    image.png

    二阶段提交是XA的标准实现,它将分布式事务的提交拆分为2个阶段:prepare和commit/rollback。

    开启XA全局事务后,所有子事务会按照本地默认的隔离级别锁定资源,并记录undo和redo日志,然后由TM发起prepare投票,询问所有的子事务是否可以进行提交。

    当所有子事务反馈的结果为“yes”时,TM再发起commit;若其中任何一个子事务反馈的结果为“no”,TM则发起rollback。

    如果在prepare阶段的反馈结果为yes,而commit的过程中出现宕机等异常时,则在节点服务重启后,可根据XA recover再次进行commit补偿,以保证数据的一致性。

    XA recover的作用是列出所有处于PREPARE阶段的XA事务。

    2PC模型中,在prepare阶段需要等待所有参与子事务的反馈,因此可能造成数据库资源锁定时间过长,不适合并发高以及子事务生命周长较长的业务场景。

    Sharding-Sphere支持基于XA的强一致性事务解决方案,可以通过SPI注入不同的第三方组件作为事务管理器实现XA协议,如Atomikos和Narayana。

    在sharding-sphere中默认使用了atomikos,编程时可以直接使用,例如注解方式为:

        @ShardingTransactionType(TransactionType.XA)
        @Transactional(rollbackFor = Exception.class)
        public void testTransactional() {
            User user1 = new User(1, "faith", 12);
            this.userDao.addOne(user1);
            User user2 = new User(2, "belief", 12);
            this.userDao.addOne(user2);
            this.userDao.addOne(user2); // 这里会报错,因为在分布式事务中,因此user1、user2都会插入失败
        }

    如果spring项目中单独使用atomikos,需要做数据源以及事务管理器等配置,例如:

        <!-- 数据源 -->
        <bean id="abstractDatasource" abstract="true"
                class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
            <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
            <property name="poolSize" value="${datasource.poolSize}" />
            <property name="minPoolSize" value="${datasource.minPoolSize}" />
            <property name="maxPoolSize" value="${datasource.maxPoolSize}" />
            <!--获取连接失败重新获等待最大时间,在这个时间内如果有可用连接,将返回 -->
            <property name="borrowConnectionTimeout" value="60" />
            <!--最大获取数据时间,如果不设置这个值,Atomikos使用默认的5分钟,那么在处理大批量数据读取的时候,一旦超过5分钟,就会抛出类似 Resultset 
                is close 的错误. -->
            <property name="reapTimeout" value="20" />
            <!--最大闲置时间,超过最小连接池连接的连接将将关闭 -->
            <property name="maxIdleTime" value="${datasource.maxIdleTime}" />
            <!--连接回收时间 -->
            <property name="maintenanceInterval" value="60" />
            <!--java数据库连接池,最大可等待获取datasouce的时间 -->
            <property name="loginTimeout" value="60" />
            <property name="logWriter" value="60" />
            <!-- <property name="maxLifetime" value="1800000"/> -->
            <property name="testQuery">
                <value>select 1</value>
            </property>
        </bean>
    
        <!-- atomikos事务管理器 -->
        <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
            init-method="init" destroy-method="close">
            <description>UserTransactionManager</description>
            <property name="forceShutdown">
                <value>true</value>
            </property>
        </bean>
    
        <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
            <property name="transactionTimeout" value="300" />
        </bean>
    
        <!-- spring 事务管理器,包装了atomikos事务 -->
        <bean id="jtaTransactionManager"
            class="org.springframework.transaction.jta.JtaTransactionManager">
            <property name="transactionManager">
                <ref bean="atomikosTransactionManager" />
            </property>
            <property name="userTransaction">
                <ref bean="atomikosUserTransaction" />
            </property>
        </bean>
    
        <!-- spring 事务模板,和spring的jta事务类似功能 -->
        <bean id="transactionTemplate"
            class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager">
                <ref bean="jtaTransactionManager" />
            </property>
        </bean>
    ]]>
    用 NetworkX + Gephi + Nebula Graph 分析<权力的游戏>人物关系(上篇)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 权力的游戏

    我们都知道《权利的游戏》在全世界都很多忠实的粉丝,除去你永远不知道剧情下一秒谁会挂这种意外“惊喜”,当中复杂交错的人物关系也是它火爆的原因之一,而本文介绍如何通过 NetworkX 访问开源的分布式图数据库 Nebula Graph,并借助可视化工具—— Gephi 来可视化分析《权力的游戏》中的复杂的人物图谱关系。

    数据集

    本文的数据集来源:冰与火之歌第一卷(至第五卷)[1]

    • 人物集 (点集):书中每个角色建模为一个点,点只有一个属性:姓名
    • 关系集(边集):如果两个角色在书中发生过直接或间接的交互,则有一条边;边只有一个属性:权重,权重的大小代表交互的强弱。

    这样的点集和边集构成一个图网络,这个网络存储在图数据库 Nebula Graph [2]中。

    社区划分——Girvan-Newman 算法

    我们使用 NetworkX [3] 内置的社区发现算法 Girvan-Newman 来为我们的图网络划分社区。

    以下为「社区发现算法 Girvan-Newman」解释:

    网络图中,连接较为紧密的部分可以被看成一个社区。每个社区内部节点之间有较为紧密的连接,而在两个社区间连接则较为稀疏。社区发现就是找到给定网络图所包含的一个个社区的过程。

    Girvan-Newman 算法即是一种基于介数的社区发现算法,其基本思想是根据边介数中心性(edge betweenness)从大到小的顺序不断地将边从网络中移除直到整个网络分解为各个社区。因此,Girvan-Newman 算法实际上是一种分裂方法。

    Girvan-Newman 算法的基本流程如下:
    (1)计算网络中所有边的边介数;
    (2)找到边介数最高的边并将它从网络中移除;
    (3)重复步骤 2,直到每个节点成为一个独立的社区为止,即网络中没有边存在。

    概念解释完毕,下面来实操下。

    1. 使用 Girvan-Newman 算法划分社区。NetworkX 示例代码如下
    comp = networkx.algorithms.community.girvan_newman(G)
    k = 7
    limited = itertools.takewhile(lambda c: len(c) <= k, comp)
    communities = list(limited)[-1]
    1. 为图中每个点添加一个 community 属性,该属性值记录该点所在的社区编号
    community_dict = {}
    community_num = 0
    for community in communities:
        for character in community:
            community_dict[character] = community_num
            community_num += 1
            nx.set_node_attributes(G, community_dict, 'community')

    节点样式——Betweenness Centrality 算法

    下面我们来调整下节点大小及节点上标注的角色姓名大小,我们使用 NetworkX 的 Betweenness Centrality 算法来决定节点大小及节点上标注的角色姓名的大小。

    图中各个节点的重要性可以通过节点的中心性(Centrality)来衡量。在不同的网络中往往采用了不同的中心性定义来描述网络中节点的重要性。Betweenness Centrality 根据有多少最短路径经过该节点,来判断一个节点的重要性。

    1. 计算每个节点的介数中心性的值
    betweenness_dict = nx.betweenness_centrality(G) # Run betweenness centrality
    1. 为图中每个点再添加一个 betweenness 属性
    nx.set_node_attributes(G, betweenness_dict, 'betweenness')

    边的粗细

    边的粗细直接由边的权重属性来决定。

    通过上面的处理,现在,我们的节点拥有 name、community、betweenness 三个属性,边只有一个权重 weight 属性。

    下面显示一下:

    import matplotlib.pyplot as plt
    color = 0
    color_map = ['red', 'blue', 'yellow', 'purple', 'black', 'green', 'pink']
    for community in communities:
        nx.draw(G, pos = nx.spring_layout(G, iterations=200), nodelist = community, node_size = 100, node_color = color_map[color])
        color += 1
    plt.savefig('./game.png')

    emmm,有点丑…

    NetworkX 可视化

    虽然 NetworkX 本身有不少可视化功能,但 Gephi [4] 的交互和可视化效果更好。

    接入可视化工具 Gephi

    现在将上面的 NetworkX 数据导出为 game.gephi 文件,并导入 Gephi。

    nx.write_gexf(G, 'game.gexf')

    Gephi 界面

    Gephi 可视化效果展示

    在 Gephi 中打开刚才导出的 game.gephi 文件,然后微调 Gephi 中的各项参数,就以得到一张满意的可视化:

    1. 将布局设置为 Force Atlas, 斥力强度改为为 500.0, 勾选上 由尺寸调整 选项可以尽量避免节点重叠:

    Force Atlas 为力引导布局,力引导布局方法能够产生相当优美的网络布局,并充分展现网络的整体结构及其自同构特征。力引导布局即模仿物理世界的引力和斥力,自动布局直到力平衡。

    Gephi 界面

    1. 给划分好的各个社区网络画上不同的颜色:

    在外观-节点-颜色-Partition 中选择 community(这里的 community 就是我们刚才为每个点添加的社区编号属性)

    Gephi 界面

    1. 决定节点及节点上标注的角色姓名的大小:

    在外观-节点-大小-Ranking 中选择 betweenness(这里的 betweenness 就是我们刚才为每个点添加的 betweenness 属性)

    Gephi 界面

    1. 边的粗细由边的权重属性来决定:

    在外观-边-大小-Ranking 中选择边的权重

    Gephi 界面

    1. 导出图片再加个头像效果

    权力的游戏

    权力的游戏

    大功告成,一张权力游戏的关系谱图上线 :) 每个节点可以看到对应的人物信息。

    下一篇

    本篇主要介绍如何使用 NetworkX,并通过 Gephi 做可视化展示。下一篇将介绍如何通过 NetworkX 访问图数据库 Nebula Graph 中的数据。

    本文的代码可以访问[5]。

    致谢:本文受工作 [6] 的启发

    Reference

    [1] https://www.kaggle.com/mmmarchetti/game-of-thrones-dataset
    [2] https://github.com/vesoft-inc/nebula
    [3] https://networkx.github.io/
    [4] https://gephi.org/
    [5] https://github.com/jievince/nx2gephi
    [6] https://www.lyonwj.com/2016/06/26/graph-of-thrones-neo4j-social-network-analysis/

    作者有话说:Hi,我是王杰,是图数据 Nebula Graph 研发工程师,希望本次的经验分享能给大家带来帮助,如有不当之处也希望能帮忙纠正,谢谢~

    ]]>
    Apache Spark™ 3.0中全新的Structured Streaming UI-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:Genmao Yu
    原文链接:https://databricks.com/blog/2020/07/29/a-look-at-the-new-structured-streaming-ui-in-apache-spark-3-0.html

    编译:邵嘉阳,计算机科学与技术大三在读,Apache Spark 中文社区志愿者


    在Apache Spark 2.0中,我们迎来了Structured Streaming——构建分布式流处理应用的最佳平台。统一的API(SQL,Dataset和DataFrame)以及Spark内置的大量函数为开发者实现复杂的需求提供了便利,比如流的聚合,流-流连接和窗口支持。开发者们普遍喜欢通过Spark Streaming中的DStream的方式来管理他们的流,那么类似的功能什么时候能在Structured Streaming中得到实现呢?这不,在Apache Spark 3.0中,全新的Structured Streaming可视化UI和开发者们见面了。

    新的Structured Streaming UI会提供一些有用的信息和统计数据,以此来监视所有流作业,便于在开发调试过程中排除故障。同时,开发者还能够获得实时的监测数据,这能使生产流程更直观。在这个新的UI中,我们会看到两组统计数据:1)流查询作业的聚合信息;2)流查询的具体统计信息,包括输入速率(Input Rate)、处理速率(Process Rate)、输入行数(Input Rows)、批处理持续时间(Batch Duration)和操作持续时间(Operation Duration)等。

    流查询作业的聚合信息

    开发者提交的流SQL查询会被列在Structured Streaming一栏中,包括正在运行的流查询(active)和已完成的流查询(completed)。结果表则会显示流查询的一些基本信息,包括查询名称、状态、ID、运行ID、提交时间、查询持续时间、最后一批的ID以及一些聚合信息,如平均输入速率和平均处理速率。流查询有三种状态:运行(RUNNING)、结束(FINISHED)、失败(FAILED)。所有结束(FINISHED)和失败(FAILED)的查询都在已完成的流式查询表中列出。Error列显示有关失败查询的详细信息。

    1.png

    我们可以通过单击Run ID链接查看流查询的详细信息。

    详细的统计信息

    Statistics页面显示了包括输入速率、处理速率、延迟和详细的操作持续时间在内的一系列指标。通过图表,开发者能全面了解已提交的流查询的状态,并且轻松地调试查询处理中的异常情况。
    2.png
    image.png

    它包含以下指标:

    • Input Rate:数据到达的聚合速率(跨所有源)。
    • Process Rate: Spark处理数据的聚合速率(跨所有源)。
    • Batch Duration: 每一批的处理时间。
    • Operation Duration: 执行各种操作所花费的时间(以毫秒为单位)。
      被追踪的操作罗列如下:
    • addBatch:从源读取微批的输入数据、对其进行处理并将批的输出写入接收器所花费的时间。这应该会占用微批处理的大部分时间。
    • getBatch:准备逻辑查询以从源读取当前微批的输入所花费的时间。
    • getOffset:查询源是否有新的输入数据所花费的时间。
    • walCommit:将偏移量写入元数据日志。
    • queryPlanning:生成执行计划。

    需要注意的是,由于数据源的类型不同,一个查询可能不会包含以上列出的所有操作。

    使用UI解决流的性能故障

    在这一部分中,我们会看到新的UI是怎样实时、直观地显示查询执行过程中的异常情况的。我们会在每个例子中预先假设一些条件,样例查询看起来是这样的:

    import java.util.UUID
    
    val bootstrapServers = ...
    val topics = ...
    val checkpointLocation = "/tmp/temporary-" + UUID.randomUUID.toString
    
    val lines = spark
        .readStream
        .format("kafka")
        .option("kafka.bootstrap.servers", bootstrapServers)
        .option("subscribe", topics)
        .load()
        .selectExpr("CAST(value AS STRING)")
        .as[String]
    
    val wordCounts = lines.flatMap(_.split(" ")).groupBy("value").count()
    
    val query = wordCounts.writeStream
        .outputMode("complete")
        .format("console")
        .option("checkpointLocation", checkpointLocation)
        .start()

    由于处理能力不足而增加延迟

    在第一种情况下,我们希望尽快处理Apache Kafka数据。在每一批中,流作业将处理Kafka中所有可用的数据。如果处理能力不足以处理批数据,那么延迟将迅速增加。最直观的现象是Input Rows和Batch Duration会呈线性上升。Process Rate提示流作业每秒最多只能处理大约8000条记录,但是当前的输入速率是每秒大约20000条记录。产生问题的原因一目了然,那么我们可以为流作业提供更多的执行资源,或者添加足够的分区来处理与生产者匹配所需的所有消费者。
    5.png

    稳定但高延迟

    第二种情况下,延迟并没有持续增加,而是保持稳定,如下截图所示:
    image.png

    我们发现在相同的Input Rate下,Process Rate可以保持稳定。这意味着作业的处理能力足以处理输入数据。然而,每批的延迟仍然高达20秒。这里,高延迟的主要原因是每个批中有太多数据,那么我们可以通过增加这个作业的并行度来减少延迟。在为Spark任务添加了10个Kafka分区和10个内核之后,我们发现延迟大约为5秒——比20秒要好得多。

    image.png

    使用操作持续时间图进行故障排除

    操作持续时间图(Operation Duration Chart)显示了执行各种操作所花费的时间(以毫秒为单位)。这对于了解每个批处理的时间分布和故障排除非常有用。让我们以Apache Spark社区中的性能改进“Spark-30915:在查找最新批处理ID时避免读取元数据日志文件“为例。
    在某次查询中我们发现,当压缩后的元数据日志很大时,下一批要花费比其他批更多的时间来处理。

    image.png

    在进行代码审查之后,我们发现这是由对压缩日志文件的不必要读取造成的并进行了修复。新的操作持续时间图确认了我们想法:

    image.png

    未来的开发方向

    如上所示,新的Structured Streaming UI将通过提供更有用的流查询信息帮助开发者更好地监视他们的流作业。作为早期发布版本,新的UI仍在开发中,并将在未来的发布中得到改进。有几个未来可以实现的功能,包括但不限于:

    • 更多的流查询执行细节:延迟数据,水印,状态数据指标等等。
    • 在Spark历史服务器中支持Structured Streaming UI。
    • 对于不寻常的情况有更明显的提示:发生延迟等。

    近期活动:

    8月24日开始 Spark 实战训练营正式开课
    免费报名链接:https://developer.aliyun.com/learning/trainingcamp/spark/2


    入群照片.png

    ]]>
    如何设计秒杀系统?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 秒杀系统是常见的一种系统设计类型,在电商业务中的应用非常普遍。同时也是面试中问得比较多的。

    如果说程序 = 算法 +数据结构的话,那么系统 = 服务 + 数据存储。所以系统设计中,我们更多的是围绕服务和数据存储来讨论。

    举个简单例子:某年双11,商家以¥4499的价格上架了某iphone,比官网价格便宜了¥1000,库存总数10台,运营设置11/11 00:00活动生效,一人只能购买1台,商品售完为止。

    梳理下用户端的流程图:

    image.png

    根据系统设计的4S分析法:

    第一步 - Scenario场景

    需要确定设计哪些功能,承受多大的访问量?

    这里有个常见概念QPS(Query Per Second),即一秒内可以处理的请求数量。假如一个服务的RT是20ms,则QPS为50,这里计算的是单机单线程QPS,如果计算集群的话,需要考虑集群数量和线程数量。

    这时候需要确认秒杀商品的请求QPS是多少。如果面试官说峰值大概量级在100万,那么按照服务单线程QPS是50,单台最大线程数按3来计算的话,单台机器最大支撑150的QPS,那么至少需要100W/150=6667台机器。

    常见的组件最大QPS,mysql单机1000QPS,Redis单机10万QPS。

    第二步 - Service服务

    秒杀系统服务设计大致如下:

    image.png

    第三步 - Storage存储

    数据是如何存储和访问的。为每个服务选择合适的存储结构,然后细化数据表结构。这个例子中,秒杀系统数据库设计如下:

    image.png

    于是我们可以得到秒杀活动中,数据库之间的关系如图所示:

    image.png

    接下来就是mysql扣库存了。秒杀系统一定会遇到的就是并发问题,这里说下乐观锁和悲观锁。
    悲观锁的流程:

    image.png

    乐观锁流程:

    image.png

    可以看到悲观锁的问题是会占用大量的线程资源,可能导致mysql的线程耗尽。在对于数据一致性要求非常高的场景中,一般用悲观锁;而乐观锁在version变动频繁的情况下则不适用,比如这里的秒杀系统就不太适合用乐观锁,因为库存变化太快了

    推荐免费体验九章的《系统架构设计》,在免费试听章节里,还讲到了Redis下的秒杀系统数据库设计。

    image.png

    几个关于Redis的常见问题:

    什么时候把库存写入到Redis?
    秒杀活动创建/维护时写入Redis。

    如何保证活动数据库和库存数据一致?
    可以使用分布式事务或消息队列。

    分布式事务:保证多个数据库的操作同时成功或者同时失败。对强一致性有要求的业务场景可以考虑使用分布式事务,比如银行转账

    image.png

    消息队列:基于生产者/消费者模型的组件,一般实现异步任务(非实时处理)时会引入消息队列。消息队列的好处是任务可以慢慢处理,不必同步处理等着响应结果。目前主流的消息队列有RocketMQ、Kafka等。使用场景除了异步任务之外,一般还用于失败的情况下重试处理,重复消费直到消费成功。

    image.png

    下单减库存/支付减库存?
    下单锁定库存,支付减库存。

    如何防止商品被超卖?
    把库存数据放入到缓存中,利用缓存的原子特性保证同时只有一个线程操作库存。

    库存写回数据库的时机?
    采用定时任务同步Redis的数据写回数据库。

    4S分析法的第四步 - Scale扩展

    对于秒杀系统来说,就是高并发场景下如何优化系统。推荐大家通过完整视频了解如何进行系统优化和升级。

    前面说过,秒杀系统是电商业务中最常见的,在实际面试当中,系统架构设计的考察标准是:面试者独立设计一个大型的系统,它在现实中是可work的,同时在高并发的情况下仍具有高可用性。

    对许多缺少工作经验的应届生,以及大量没机会参与大型系统设计、日常都在“拧螺丝”的初级程序员来说,明明不具备架构的能力但面试的时候又经常遇到,拿到设计类题目就懵了!

    九章的《系统架构设计》覆盖了18大系统架构设计知识点和面试题。通过大量调研,囊括了最热门、高频的18大设计类题型包括了:

    • 秒杀/订票系统
    • IM系统
    • 协同文档编辑系统
    • 谷歌搜索系统
    • 用户登陆/注册系统
    • 网站系统(API设计/短URL)
    • 数据库
    • 容器技术(K8S/Docker)
    • MLE机器学习系统
      ……

    想要尝试自己设计一个秒杀系统,或者对国内热门的系统设计感兴趣的同学们,不妨来试听一下,好的学习方式永远是自己动手实现。

    ]]>
    微服务技术栈:API网关中心,落地实现方案-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、服务网关简介

    1、外观模式

    客户端与各个业务子系统的通信必须通过一个统一的外观对象进行,外观模式提供一个高层次的接口,使得子系统更易于使用:

    03-1.png

    简单说一下外观模式,网关和这个模式很像,但是比外观模式复杂,模式,结构,原则这些都是通用的,在各种架构或组件中使用。

    2、网关简介

    微服务网关从感觉上,很像是:拦截器+路由+过滤器,拦截请求,系列基础处理,路由转发到指定服务。

    服务网关在整个架构体系上也是一个服务器,作为请求的唯一入口,与外观模式十分类似,在网关层处理所有的非业务功能,为客户端提供定制的API,在网关层通常会执行如下操作:如权限校验、监控、负载均衡、缓存、日志、限流、等等。

    二、网关模式

    1、模式对比

    这里对比常用的请求服务管理模式,和网关模式,如图:

    03-2.png

    常规模式

    在没有网关的情况下,微服务架构会在业务层服务上提供一个API服务,用来接收参数,例如Client-API,通常会根据系统模块划分多个API,例如,运营系统,用户系统等。

    • 请求统一进入Client-API服务 ;
    • Client-API经过鉴权,限流,路由等操作;
    • 如果请求通过,会转发到相应业务服务上;
    • 如果请求被拦截,会直接返回给客户端;
    • Client-API集成所有业务服务的开放接口;

    该模式下的缺点非常明显,每个Client-API都需要实现一套非业务服务,代码冗余,当系统膨胀之后,维护成本极高,适用于轻量级系统架构。

    网关模式

    在业务服务层上,添加一层网关控制,在服务网关中可以完成一系列的横切非业务功能:

    • 客户端请求在网关层做统一拦截;
    • 网关上执行:路由/鉴权/限流/降级等操作;
    • 网关判断是转发请求还是直接响应客户端;

    网关服务层要执行很多非业务流程,作为系统的服务端唯一入口,承受所有服务的路由转发,安全,限流,缓存,日志,监控,熔断降级等功能,网关服务不仅要做到高可用,还要避免出现性能瓶颈。

    2、多重网关

    在大型复杂的系统中,通常会对网关做分层管理,把一类业务规划到一个网关下,避免网关过于臃肿,方便维护和管理:

    03-3.png

    总网关:通用常用来做路由转发功能;

    模块网关:分类的业务服务聚合网关,对这类服务的做非业务性操作,最后请求转发到具体服务上,在数据类平台上,通常对数据通道(流入流出)做一层独立的服务网关;对数据分析类服务做一层独立网关;基本是根据服务的使用情况来划分,这样避免单层服务网关过于复杂的情况。

    三、核心功能

    1、配置层面

    服务发现

    网关应该有服务发现功能,通过统一注册中心,获取服务列表,这样才能执行统一代理服务和路由转发功能。

    路由请求

    植入网关层服务之后,客户端不知道自己请求的是哪个具体的服务,只需要把请求转发给网关,网关放行之后会把请求路由到指定业务服务上。

    负载均衡

    网关连接的服务实例可能是集群模式存在,所以网关还可以对各个服务实例上执行负载均衡策略,常见的策略就是服务轮询或者按权重路由。

    2、定制开发

    定制开发例如:权限校验,日志集成,接口限流,等相关功能,需要和数据库交互,可以做成独立服务,在服务中实现具体的处理逻辑,网关层直接调用即可。

    四、网关组件

    1、Netflix-Zuul

    Zuul网关主要提供动态路由,监控,弹性,安全管控等功能。在分布式的微服务系统中,系统被拆为了多个微服务模块,通过zuul网关对用户的请求进行路由,转发到具体的后微服务模块中,Netflix开源的一个基于JVM路由和服务端的负载均衡器。

    2、Tyk组件

    Tyk是一个开源的、轻量级的、快速可伸缩的API网关,支持配额和速度限制,支持认证和数据分析,支持多用户多组织。基于go语言编写,在Java架构系统中使用很少。

    3、Kong组件

    Kong是一款基于Nginx+Lua编写的高可用,可扩展的开源网关项目,由Mashape公司开放。核心是实现数据库抽象,路由和插件管理,插件可以存在于单独的代码库中,并且可以在几行代码中注入到请求生命周期的任何位置。提供易于使用的RESTfulAPI来操作和配置API管理,并且可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对高并发的网络请求。

    ]]>
    基于 IoT+TSDB+Quick BI 云产品架构的楼宇环境监控实战-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 今天给大家带来基于阿里云 IoT 物联网平台 + TSDB 时序时空数据库 + Quick BI 报表三大云产品组合实现楼宇环境监控端到端开发实战。


    少啰嗦,先看效果。
    image.png
    部署后效果


       0.技术架构   


    本次 IoT 物联网开发实战我们在室内部署 4 个温湿度传感器,实时采集数据,每10秒发送到阿里云 IoT 物联网平台,通过规则引擎写入 TSDB时序数据库。在Quick BI 工作台,创建数据报表以分钟维度展示室内温湿度变化曲线。

    技术架构如下:
    image.png


       1.物联网平台开发   


    1.1.免费开通阿里云 IoT物联网云服务:
    https://www.aliyun.com/product/iot-deviceconnect
    />



    1.2.创建产品室内温湿度计器,选择自定义品类,直连设备,定义物模型,包含2个属性温度,湿度:
    image.png


    1.3.注册设备,获取身份三元组。


    image.png


    1.4.配置规则引擎,实时流转数据到 TSDB中
    image.png


    1.5.完成设备端开发,实时上报温湿度数据。
    我们以Node.js脚本来模拟设备上报,代码如下:

    // 依赖mqtt库
    const mqtt = require('aliyun-iot-mqtt');
    // 设备身份
    var options = {
        productKey: "device productKey",
        deviceName: "device deviceName",
        deviceSecret: "device deviceSecret",
        regionId: "cn-shanghai"
    };
    
    // 建立连接
    const client = mqtt.getAliyunIotMqttClient(options);
    
    //模拟 设备 上报数据(原始报文)
    setInterval(function() {
        client.publish(
            `/sys/${options.productKey}/${options.deviceName}/thing/event/property/post`
            , getPostData()
            );
    
    }, 10 * 1000);
    
    // 模拟 温湿度
    function getPostData() {
    
        const payload = {
            id: Date.now(),
            version:"1.0",
            params: {
                temperature: 10+Math.floor(Math.random() * Math.floor(50)),
                humidity: 10+Math.floor(Math.random() * Math.floor(50))
            },
            method: "thing.event.property.post"
        }
    
        console.log("payload=[ " + payload + " ]")
        return JSON.stringify(payload);
    }

       2.TSDB数据库   


    2.1.创建时序数据库,并开通公网 TSQL 连接串
    image.png


    2.2.IoT设备数据写入TSDB的记录
    image.png


       3.Quick BI   


    3.1.开通Quick BI服务,添加数据源,输入TSDB连接参数。
    image.png
    数据源添加成功
    image.png


    3.2.基于数据源的温度和湿度指标,创建数据集。
    image.png


    3.3.创建数据仪表板,并根据业务需求编辑图表。
    image.png


    3.4.发布仪表板。
    image.png


    3.5.在浏览器中查看楼宇环境监控报表。
    image.png
     
    【往期回顾】
    1、39张IoT传感器工作原理GIF图汇总
    2、IoT 设备发送 MQTT 请求的曲折经历
    3、20元体 Arduino 环境监测仪开发
    4、智能手持测温枪开发实践
    5、JMeter 压测 MQTT 服务性能实战


    ]]>
    新手如何选择阿里云操作系统?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 随着网站服务器技术的发展,越来越多的站长建站首先选择云服务器。时下阿里云云服务器ECS脱颖而出,成为多数站长网站服务器的首选。那么对于刚刚接触云服务器的站长来说,如何选择适合网站的阿里云云服务器ECS操作系统,阿里云云服务器ECS的操作系统有什么区别,阿里云linux服务器和windows服务器有何不同呢。

    前提:若后期有需求购买阿里云任何产品的朋友,可以提前领取优惠劵。后期可为大家减少成本:点击领取阿里云优惠劵

    阿里云个人购买+阿里云企业购买

    首先,我们要清楚的便是每个系统之间的差别,以及在阿里云上的差别:

    1.Windows

    1.1)系统内含正版激活。

    1.2)适合于运行Windows下开发的程序,如.net等。

    1.3)支持SQLServer等数据库(需自行安装)。

    1.4)可以使用远程桌面方式登录进行管理。

    注:512内存不支持选择Windows系统,1G以上内存才能很好支持该系统。

    2.Linux

    2.1.1)最流行的服务器端操作系统,强大的安全性和稳定性。

    2.1.2)免费且开源,轻松建立和编译源代码。

    2.1.3)通过SSH方式远程访问您的云服务器。

    2.1.4)一般用于高性能web等服务器应用,支持常见的PHP/Python等编程语言,支持MySQL等数据库(需自行安装)。

    2.2CentOS(推荐)请使用yum方式在线安装软件。

    2.3Ubuntu请使用aptitude方式在线安装软件。

    2.4Debian请使用apt-get方式在线安装软件。

    2.5AliyunLinux(兼容RedHat)请使用yum方式在线安装软件,yum源需要自行购买redhat的商业支持。

    操作系统更换规则:

    1.更换操作系统

    更换系统之前请先停止云服务器,云服务器更换操作系统会直接重置系统盘【IP不变】,系统盘数据将会丢失!

    请您注意:

    1.1.更换操作系统会使云服务器的系统盘更换为新的镜像,原有系统盘的数据都会丢失。

    1.2.云服务器数据盘的数据不会受到影响。

    1.3.建议您将系统盘的个人数据备份到数据盘中,或采用其他方式进行备份。

    1.4.因您没有备份系统盘相关个人数据而造成的数据丢失,阿里云不承担责任。

    1.5.内存为512M云服务器不支持更换Windows操作系统。

    2.CPU/内存与操作系统的选择

    2.1)如需选择/变更4G以上内存请您选择64位操作系统(32位操作系统存在寻址限制)。

    2.2)如您选择32位操作系统,4G以上内存页面暂不展示,只有云服务器更换为64位操作系统才可展示。

    2.3)Windows32位操作系统支持最高CPU为4核。

    2.4)配置:[CPU:1核;内存:512M]的云服务器不支持选择/更换Windows操作系统。

    Windows篇

    阿里云提供了6种window系统,涵盖了Server2003sp2以及Server2008R2这两大类操作系统。

    其中又分为了32位和64位

    (1)如何选择32位还是64位

    32位系统相比64位系统,最主要的限制体现在内存的大小上。因为32位本身的限制,其最大只可支持到4GB内存,如果您的网站要使用高于4GB的内存或者以后有扩充内存寻到4GB以上的打算,请使用64位操作系统。

    (2)选择2003还是选择2008

    对于windows来说,我个人建议是选择版本越高的越好。相对来说新版本漏洞相对来说更少,而且IIS7.5相对于IIS6提供了更多的功能以及更方便的控制台。但是考虑到大家的机器配置不同,在此给出一下几种选择:

    A:配置低于双核2GB内存:选择server2003不装数据库配置双核4GB:server2003mssql或者server2008R2不带数据库

    B:配置高于双核8GB:serever2008R2mssql建议如果大家要在云服务器上跑数据库,尽量选择大内存配置,或者降低配置去选用RDS

    (3)中英文、安全加固版如何选择

    这个就依据大家各自的喜好来了,在此不多说了至于Windows服务器配置教程,因为网上教程很多而且相对于Linux来说Windows配置难度更低,所以Windows的配置教程会比较晚的放出。

    Linux篇

    (1)这些linux大类有什么区别

    Debian:用的deb包,使用APT包管理系统。

    同时Debian提供了大多数软件比较新的版本,并且提供了更多的软件包(相对于原版RedHat)。Debian的优点在于更新迅速,软件包完善(Ubuntu尤其),操作便利。缺点是部分时候稳定性欠佳,跟进最新软件有可能存在Bug。

    Centos:用rpm包,使用yum包管理系统。

    相对于Debian来说,Centost的一大特点就是慢。大部分软件停留在稳定版本,而且相距最新版版本也差较多。而且某些新版软件的一些新特性支持也比较慢,比如php-fpm。

    因为Centos是面向企业用户提供的操作系统,所以在稳定性上十分突出,一般在新功能或稳定性的选择上更倾向于后者。只有当某个功能完全确定稳定了,才会加入到系统里。优点是系统稳定,技术文档完善,如果付费的话能得到企业级别的技术支持。缺点是软件包比较老旧,而且一些较新功能会欠缺。

    总结一下:如果你喜欢尝鲜,喜欢用最新的功能或喜欢折腾系统,那么Debian是个更好的选择。
    上手难度Ubunt

    (2)Debian与Ubuntu的选择

    Ubuntu是基于Debian所开发,可以简单地认为Ubuntu是Debian的功能加强版。

    与Debian相比,Ubuntu提供了更人性化系统配置,更强大的系统操作以及比Debian更激进的软件更新。

    Ubuntu与Debian比较,可以认为Debian更趋向于保守一些,Ubuntu对新手友好度更高,上手更容易。

    用过Ubuntu的都会体会到它的易用,反之如果用过Ubuntu再换到别的系统,都会觉得不适应,Ubuntu真的很方便。

    个人建议,如果你打算选择Debian类的,建议选择Ubuntu。

    Ubuntu提供了更好的操作,更激进的软件更新,更方便管理软件以及相差无几的稳定性。

    如果你不想放弃稳定'那么请选择Debian。

    关于Ubuntu版本选择:

    在此解释下Ubuntu的版本支持时间。Ubuntu普通版本只提供18个月的技术支持,过期则不管。

    服务器版本提供长达五年的技术支持。所以建议大家选择12.04版,提供长达5年的技术支持,可以确保在静候相当长的一段时间内你的服务器可以继续收到系统升级补丁以及可用的软件源。

    (3)Centos的选择

    对于阿里云Centos的选择,建议选择Centos6.5版本,带来了更多的新特性以及更多的新功能。

    除非你的软件需要php5.1的环境,那么就选择Centos6.5。如果网站需要支持php5.1,只能选用Centos5.8。

    至于具体版本选择,建议php5.1用户选择Centos5.8,其他的用户则为Centos6.5。
    最后的最后提醒大家一定要领取价值2000优惠劵

    ]]>
    2020年阿里云热门活动全攻略-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 口碑爆款,低至0.9折

    千万用户的口碑之选,热门云产品鼎力推荐,跟着买就对了查看更多

    精选爆款弹性计算云数据库网络存储CDN云安全企业应用更多产品

    ECS计算型 c5企业新用户销量之王!适用Web服务器、广告、游戏等企业通用业务场景2核4G1年1~10M可选带宽40-100G可选云盘年中促限购3台低至2.6折¥699.84/年起¥1892.16/年加入购物车立即购买 ECS计算型c6CPU与内存配比1:2 最新代企业级产品,性能全面升级2核4G1M带宽40G高效云盘年中促限购5台河源1年7折¥1948.20/年起¥739.80/年加入购物车立即购买 ECS突发性能 t5新用户20%性能基线,性价比之王!个人开发者建站必备1核2G1年1~5M可选带宽40G高效云盘年中促限购3台低至0.9折¥91.80/年起¥928.20/年加入购物车立即购买 ECS通用型g6CPU与内存配比1:4 最新代企业级产品,性能全面提升2核8G1M带宽40G高效云盘年中促限购5台河源1年7折¥2393.40/年起¥930.60/年加入购物车立即购买
    云数据库MySQL新用户全球最受欢迎的开源数据库之一 高安全等级,高稳定,5倍性能提升基础版:1核2GAliSQL源码优化性能相比开源提升5倍多系列规格,满足多负载和可用性需求年中促半年6折限1次限1台¥583.20/6月起¥388.80/6月加入购物车立即购买 云数据库SQL Server新用户微软SQL Server 企业版授权许可 主实例故障后自动切换至镜像实例Web版:4核8G自动监控巡警,定期性能巡检随时进行备份,秒级数据恢复功能年中促半年6折限1次限1台¥2484.00/6月起¥1656.00/6月加入购物车立即购买 轻量应用服务器一键上云,可视化面板 适用于个人用户建站或企业网站搭建1核1G,SSD 40GB10Mbps最高峰值带宽2000GB最高免费流量年中促个人必选¥612.00/年起¥108.00/年加入购物车立即购买 弹性公网IP独立公网IP, 可随时绑定与解绑5Mbps带宽独立公网带宽随时绑定与解绑即开即用, 变更配置实时生效年中促年付低至8折¥1245.00/年起¥255.00/年加入购物车立即购买查看更多商品

    弹性计算

    云数据库

    网络存储CDN

    云安全

    企业应用

    更多产品

    为你推荐-上云必备

    基于您的业务场景,为您推荐解决方案及产品组合,帮助您一站式解决上云问题

    电商专区企业初创服务数字新基建业务安全企业应用音视频专区

    快速建站适合新手快速建站,价格实惠,快速上云价格实惠,ECS服务器最低0.9折快速建站,一键上云100元拥有网站,错过再等一年 ECS突发性能 t5新用户20%性能基线,性价比之王!个人开发者建站必备1核2G1年1~5M可选带宽40G高效云盘年中促限购3台低至0.9折¥91.80/年起¥928.20/年加入购物车立即购买 ECS共享型 s6新用户最新代产品,性能强劲,广泛适用于建站等应用2核4G1年1~5M可选带宽40G高效云盘年中促限购3台低至2.2折¥414.48/年起¥1469.52/年加入购物车立即购买 ECS共享型 s6新用户最新代产品,性能强劲,广泛适用于建站等应用2核4G3年1~5M可选带宽40G高效云盘年中促限购3台低至2折¥1243.44/3年起¥4408.56/3年加入购物车立即购买
    全站提速适用企业应用,2核4G及以上配置+OSS+企业版DNS+PolarDB ,打开更快ECS服务器低至1.6折,速抢~云解析保障网站访问快速、安全、稳定OSS及PolarDB读写速度更快 ECS共享型 s6企业新用户最新代产品,性能强劲,广泛适用于建站等应用2核4G1年1~10M可选带宽40~100G高效云盘年中促限购3台低至1.6折¥301.44/年起¥1582.56/年加入购物车立即购买 ECS共享型 s6企业新用户最新代产品,性能强劲,广泛适用于建站等应用2核4G3年1~10M可选带宽40~100G高效云盘年中促限购3台低至1.6折¥904.32/3年起¥4747.68/3年加入购物车立即购买 对象存储OSS适合图片/音视频等多媒体数据存储,数据实时处理,海量存储无上限标准(LRS)包500GB1年深度集成数据处理服务生命周期管理降低成本年中促折上9折¥437.40/年起¥210.60/年加入购物车立即购买查看更多商品
    畅快购物如何支撑业务快速发展?更强大性能更优的服务器,丰富的配套,保障用户畅快购物网站监控及日志分析必备工具快速识别身份证等卡证信息高精准低成本营销通路 ECS通用型g6CPU与内存配比1:4 最新代企业级产品,性能全面提升2核8G1M带宽40G高效云盘年中促限购5台河源1年7折¥2393.40/年起¥930.60/年加入购物车立即购买 ECS内存型r6CPU与内存配比1:8 最新代企业级产品,性能全面提升2核16G1M带宽40G高效云盘年中促限购5台河源1年7折¥3048.60/年起¥1211.40/年加入购物车立即购买 消息队列 Kafka 版100%兼容kafka(0.10.0.0及以上版本)标准版比自建更便宜进一步优化开源痛点开箱即用,无缝迁移年中促大数据必备¥9.90/月起¥1573.60/月加入购物车立即购买查看更多商品

    企业初创服务

    数字新基建

    业务安全

    企业应用

    音视频专区

    查看更多

    更多活动

    ]]>
    SpringCloud--Eureka集群与原理-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Eureka集群与原理

    原理

    Eureka Server注册中心的集群和Dubbo的ZooKeeper注册中心集群在结构上有很大的不同。

      Eureka Server注册中心集群中每个节点都是平等的,集群中的所有节点同时对外提供服务的发现和注册等功能。同时集群中每个Eureka Server节点又是一个微服务,也就是说,每个节点都可以在集群中的其他节点上注册当前服务。又因为每个节点都是注册中心,所以节点之间又可以相互注册当前节点中已注册的服务,并发现其他节点中已注册的服务。
    

    • CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。
      由于分布式系统中必须保证分区容错性,因此我们只能在A和C之间进行权衡。

    Zookeeper保证的是CP, 而Eureka则是保证AP。


    组件调用关系

    搭建Eureka注册中心集群,实现负载均衡+故障容错。

    Eureka集群:==相互注册,相互守望。==

    服务提供者

    1、启动后,向注册中心发起register请求,注册服务

    2、在运行过程中,定时向注册中心发送renew心跳,证明“我还活着”。

    3、停止服务提供者,向注册中心发起cancel请求,清空当前服务注册信息。

    服务消费者

    1、启动后,从注册中心拉取服务注册信息

    2、在运行过程中,定时更新服务注册信息。

    3、服务消费者发起远程调用


    数据存储结构

    既然是服务注册中心,必然要存储服务的信息,我们知道ZK是将服务信息保存在树形节点上。而下面是Eureka的数据存储结构:

    在这里插入图片描述

    • Eureka的数据存储分了两层:数据存储层和缓存层。Eureka Client在拉取服务信息时,先从缓存层获取(相当于Redis),如果获取不到,先把数据存储层的数据加载到缓存中(相当于Mysql),再从缓存中获取。值得注意的是,数据存储层的数据结构是服务信息,而缓存中保存的是经过处理加工过的、可以直接传输到Eureka Client的数据结构。

    集群

    在这里插入图片描述
    我们使用笔记本 配置 Eureka 集群

    • 修改映射配置添加进hosts文件

    mac系统的,接下来写的步骤是mac的。打开终端,输入sudo vim /etc/hosts来修改hosts文件。(权限不够需要加上sudo并输入密码)
    在最后一行加入:

    127.0.0.1       eureka7001.com
    127.0.0.1       eureka7002.com
    127.0.0.1        eureka7003.com

    然后:wq!保存退出。

    • 修改 Eureka 7001 的 yml 配置
    eureka:
      instance:
        hostname: eureka7001.com  #eureka服务端的实例名称
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          #集群版  相互注册,相互守望
          defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/ 
          
    
    • 修改 Eureka 7002 的 yml 配置
    eureka:
      instance:
        hostname: eureka7002.com  #eureka服务端的实例名称
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          #集群版  相互注册,相互守望
          defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7003.com:7003/eureka/ #相互注册,相互守望
    
    • 修改 Eureka 7003 的 yml 配置
    eureka:
      instance:
        hostname: eureka7003.com  #eureka服务端的实例名称
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          #集群版  相互注册,相互守望
          defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/ #相互注册,相互守望
    

    在这里插入图片描述
    在这里插入图片描述

    • 将客户端注册到 eureka 集群
    defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka

    在这里插入图片描述


    actuator微服务信息完善

    修改 eureka client 的yml文件:

    # client:    
    #     ...    instance要和client对齐
      instance:
        instance-id: payment8001
        prefer-ip-address: true   #访问路径可以显示ip地址

    修改前:

    在这里插入图片描述

    修改后:
    在这里插入图片描述


    Eureka自我保护

    保护模式主要用于一组客户端和Eureka Server 之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其注册表的信息,不再删除服务注册表的数据,也就是不会注销任何微服务
    在这里插入图片描述
    导致原因 :某时刻某一个微服务不可用了 ,Eureka不会立刻清理,依旧会对该微服务对信息进行保存,属于CAP里面对AP分支

    • Eureka自我保护机制

        为了防止EurekaClient 可以正常运行,但是Server 在网络不通对情况下,Server不会立即剔除Client 
      
      ![在默认的情况下,如果EurekaServer 在一定时间内没有接收到某个微服务的实例心跳,EurekaServer将注销该实例  (默认90秒)。](https://img-blog.csdnimg.cn/20200819225642530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxOTc3ODM4,size_16,color_FFFFFF,t_70#pic_center)

      只有在一定时间内丢失大量服务的心跳才开启自我保护模式。

    在这里插入图片描述

    禁止自我保护

      server:
        #关闭自我保护,默认为true
        enable-self-preservation: false
        #心跳的间隔时间,单位毫秒
        eviction-interval-timer-in-ms: 2000
    
       #Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
       lease-renewal-interval-in-seconds: 1
       #Eureka服务端在收到最后一次心跳后等待的时间上限,单位秒(默认90秒),超时剔除服务
       lease-expiration-duration-in-seconds: 2
    

    ]]>
    一文读懂jar包的小秘密-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 简介

    java程序员每天不是在创建jar包就是在创建jar包的路上,并且各种依赖引用都是以jar包的形式展示的。但是随着现代IDE的出现,我想很多程序员已经基本上很少直接和jar包打交道了。

    换句话说,他们已经不认识jar包了。

    那么jar包到底是什么呢?它有哪些小秘密呢?一起来看一下吧。

    jar包到底是什么

    jar包其实是一种zip格式的文件,所以说你实际上是可以使用zip相关的命令来对jar包进行创建或者解压缩操作。

    不同的是jar包中多了一个META-INF文件夹。通过这个文件夹,jar包可以执行更多的操作。

    JDK也自带了一个jar命令,通过jar命令我们可以实现创建,更新jar包的操作,下图是JDK8中jar命令的说明:

    因为JDK9之后引入了模块化的概念,所以JDK9之后jar命令有了比较大的变化:

    我们看一下JDK14中的jar命令的用法:

    这里主要不是讲jar命令,所以我们不具体展开。

    META-INF目录

    jar包和zip包最大的区别就在于jar包中包含了META-INF目录(不是必须的),我们看一个比较常用的lombok.jar包的结构是怎么样的:

    这个版本比较新,所以它使用的是最新的JPMS的写法,大家可以看到在jar包的根目录下面有一个module-info.class文件,表示这个jar包使用的是模块化。

    然后再看一下META-INF目录,里面有一个MANIFEST.MF文件:

    Manifest-Version: 1.0
    Ant-Version: Apache Ant 1.7.1
    Created-By: 14.3-b01-101 (Apple Inc.)
    Premain-Class: lombok.launch.Agent
    Agent-Class: lombok.launch.Agent
    Can-Redefine-Classes: true
    Main-Class: lombok.launch.Main
    Lombok-Version: 1.18.10

    MANIFEST.MF主要用来定义package相关的数据,这里我们可以看到lombok的MANIFEST.MF文件定义了manifest的版本号,创建时间,版本号和几个类型的class。

    services文件夹里面存放的可以对外提供的服务。

    这里列出的文件并不全,实际上还有下面几种文件:

    • INDEX.LIST

    可以使用 -i在生成jar包的时候自动创建,是class的index文件,主要用来加速class加载。

    • x.SF

    JAR包的签名文件。

    • x.DSA

    与具有相同基本文件名的签名文件关联的签名块文件。 该文件存储相应签名文件的数字签名。

    • versions/

    主要为使用多版本的特性准备的,里面存储的是不同版本的class和资源。

    比如下面命令创建了多个版本发行的jar包,并且将一些文件放在 META-INF/versions/9 目录中。

     jar --create --file mr.jar -C foo classes --release 9 -C foo9 classes

    module-info.class

    假如我们使用的是JDK9之后的JPMS模块化,那么就会生成这么一个module-info.class。这个文件主要是描述模块和外部模块直接的关系。

    看一下lombok的例子:

    module lombok {
        requires java.compiler;
        requires java.instrument;
        requires jdk.unsupported;
        requires static org.mapstruct.processor;
    
        exports lombok;
        exports lombok.experimental;
        exports lombok.extern.apachecommons;
        exports lombok.extern.java;
        exports lombok.extern.jbosslog;
        exports lombok.extern.log4j;
        exports lombok.extern.slf4j;
        exports lombok.extern.flogger;
    
        provides javax.annotation.processing.Processor with lombok.launch.AnnotationProcessorHider$AnnotationProcessor;
        provides org.mapstruct.ap.spi.AstModifyingAnnotationProcessor with lombok.launch.AnnotationProcessorHider$AstModificationNotifier;
    }

    这里面我们定义了依赖的类和service providers,同时也定义了对外提供的类。

    在JDK9之后,存在两种path,一种是之前的class path,一种是module path。当 modular JAR被部署在module path中的时候,它就是一个modular JAR。当他被部署在class path中的时候,就是一个non-modular JAR。

    同样的,如果是一个non-modular JAR被定义在module path中,那么这个non-modular JAR就自动被转换成了一个automatic module。

    如果jar包在MANIFEST.MF中定义了Automatic-Module-Name,那么module名字就是这个值,否则会从JAR的名字来定义这个module。

    automatic module主要是为了向下兼容而产生的。

    关于JPMS的更多信息可以参考我之前写的文章:JDK9的新特性:JPMS模块化.

    versions

    versions主要和 multi-release JAR一起使用的:

    Multi-Release: true

    所谓multi-release JAR就是说一个jar包可以支持不同版本的JDK。我们可以根据需要指定不同版本的JDK所依赖的class文件或者属性文件。这个特性在我们进行JDK升级的时候还是很有帮助的。

    一般来说,目录结构是这样的:META-INF/versions/N

    其中N表示的是JDK的主要发行版本,比如9,10,11等。

    类加载器会先去META-INF/versions/N目录中加载所需要的class,然后会去其他的低版本的META-INF/versions/N目录中加载所需要的class,最后才会在META-INF/的根目录加载其他的class文件。

    MANIFEST.MF详解

    MANIFEST.MF中存放的是key:value格式的配置信息,这些配置信息又可以分成两部分,第一部分是main-section信息,第二部分是individual-section。

    我们举个简单的例子:

    Manifest-Version: 1.0
    Created-By: 1.8 (Oracle Inc.)
    Sealed: true
    Name: foo/bar/
    Sealed: false

    其中

    Manifest-Version: 1.0
    Created-By: 1.8 (Oracle Inc.)
    Sealed: true

    就是main-section信息,我们用一张图来看一下main-section的信息有哪些:

    在main-section信息下发可以接一个Name: Value,表示开启独立的针对于具体entry的属性(Per-Entry Attributes)配置:

    Name: foo/bar/
    Sealed: false

    比如上面的属性是专门针对于包foo/bar/的,并且设置其Sealed属性为false。

    Per-Entry Attributes除了 package versioning 和 sealing信息外,还可以定义Content-Type,Java-Bean,x-Digest-y和Magic属性。

    JAR包签名

    JAR包可以通过使用jarsigner来对其进行签名。和签名相关的文件是:

    • META-INF/MANIFEST.MF
    • META-INF/*.SF
    • META-INF/*.DSA
    • META-INF/*.RSA
    • META-INF/SIG-*

    签名过后的jar跟原来的jar其实并没有什么不同,只不过在META-INF/文件夹中多出了两个文件,一个是签名文件,一个是签名block文件。

    签名文件

    签名文件是以.SF结尾的,这个文件和MANIFEST.MF很类似,可以指定Signature-Version和Created-By。

    除此之外,还可以指定和安全相关的属性:

    • x-Digest-Manifest-Main-Attributes: 其中x是java.security.MessageDigest中指定的算法,表示的主要属性的摘要。
    • x-Digest-Manifest: 表示的是整个manifest的摘要。

    这两个属性主要用来做验证签名用的。

    举个例子:

    如果我们的manifest是下面这样的:

        Manifest-Version: 1.0
        Created-By: 1.8.0 (Oracle Inc.)
    
        Name: common/class1.class
        SHA-256-Digest: (base64 representation of SHA-256 digest)
    
        Name: common/class2.class
        SHA1-Digest: (base64 representation of SHA1 digest)
        SHA-256-Digest: (base64 representation of SHA-256 digest)

    那么相应的签名文件应该是这样的:

        Signature-Version: 1.0
        SHA-256-Digest-Manifest: (base64 representation of SHA-256 digest)
        SHA-256-Digest-Manifest-Main-Attributes: (base64 representation of SHA-256 digest)
    
        Name: common/class1.class
        SHA-256-Digest: (base64 representation of SHA-256 digest)
    
        Name: common/class2.class
        SHA-256-Digest: (base64 representation of SHA-256 digest)

    签名文件的摘要

    如果再对.SF文件进行摘要,那么就会得到签名文件的摘要文件:

    • .RSA (PKCS7 signature, SHA-256 + RSA)
    • .DSA (PKCS7 signature, DSA)

    Sealed

    上面我们讲到了一个Sealed属性:

    Name: javax/servlet/internal/
    Sealed: true

    这个属性的意思是,javax/servlet/internal/包中的所有类必须从这个jar包中加载。

    这个属性主要是从jar包的安全性来考虑的。

    本文已收录于 http://www.flydean.com/java-jar-in-detail/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

    ]]>
    阿里云云原生数据湖分析DLA重磅发布-数据湖管理,助力企业一站式管理OSS数据湖存储数据 -阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、什么是数据湖方案

    数据湖当前在国内外是比较热的方案,MarketsandMarkets市场调研显示预计数据湖市场规模在2024年会从2019年的79亿美金增长到201美金。一些企业已经构建了自己的云原生数据湖方案,有效解决了业务痛点;还有很多企业在构建或者计划构建自己的数据湖,Gartner 2020年发布的报告显示目前已经有39%的用户在使用数据湖,34%的用户考虑在1年内使用数据湖。在构建自己的数据湖之前还是需要充分评估什么是数据湖、数据湖方案能够带来什么价值、如何快速构建数据湖。

    1.1 什么是数据湖

    Wikipedia上说数据湖是一类存储数据自然/原始格式的系统或存储,通常是对象块或者文件,包括原始系统所产生的原始数据拷贝以及为了各类任务而产生的转换数据,包括来自于关系型数据库中的结构化数据(行和列)、半结构化数据(如CSV、日志、XML、JSON)、非结构化数据(如email、文档、PDF等)和二进制数据(如图像、音频、视频)。
    从上面可以总结出数据湖具有以下特性:

    • 数据来源:原始数据、转换数据
    • 数据类型:结构化数据、半结构化数据、非结构化数据、二进制
    • 数据湖存储:可扩展的海量数据存储服务

    1.2 数据湖方案价值

    数据湖的定义可以看出相比较数据库、数据仓库等,数据湖要处理的数据类型更加开放、更加复杂。数据库主要是处理结构化数据的联机事务;数据仓库主要处理大数据量结构化数据的分析。而数据湖主要是对海量的结构化、半结构化、非结构化、二进制数据进行存储,同时还需要对这些数据进行管理和价值挖掘。接下来可以看下云上沉淀的典型数据湖方案:

    方案一:一站式端到端数据湖存储、管理、分析&计算方案

    • 场景:企业在构建数据湖方案时,期望构建完整、通用、可扩展的解决方案,涉及到数据摄入、数据存储、数据管理、数据价值挖掘全链路,同时需要有上下游工具的支持。
    • 方案价值:数据摄入侧支持一键建湖、流式入湖归档数据到OSS存储;数据管理侧支持对Database、文本、流式数据统一到OSS上面构建元数据管理;数据分析及计算侧支持通过Serverless Spark进行ETL及复杂计算、Serverless SQL(兼容 Presto)进行交互式查询。工具对接侧支持对接DMS调度、业务APP、QuickBI来进行管理。
      方案一.png

    方案二:OSS 大规模数据(自由编程)清洗&机器学习方案

    • 场景:企业对存储在OSS上面的大规模数据需要进行多种计算负载处理,比如ETL、机器学习、图计算等,同时利用python、java、scala、R等生态进行自由编程。
    • 方案价值:DLA Serverless Spark能够友好的支持该场景。弹性方面Serverless Spark完全弹性,1分钟启动300个节点进行计算;生态方面Serverless Spark的多数据源能力,提供外部数据源批量入库、联邦分析能力;算法及Code方面支持 Python、用户Code、机器学习等原生KPI;离线数仓(复杂分析)方面支持复杂分析,提供天/月级别的报表等。
      spark方案.jpg

    方案三:不同类型数据源联邦查询分析方案

    • 场景:企业的业务系统数据一般存储在数据库比如MySQL、MongoDB等;日志数据因为数据量大的特性会存储在OSS上面。通过数据湖分析方案能够让这两种数据进行联邦查询,释放数据价值。
    • 方案价值:DLA Serverless SQL(兼容Presto)支持15种以上的数据源,能够满足95%的联邦分析数据源对接。DLA Serverless SQL支持高效的交互式查询,在读写数据源端做了大量下推优化。DLA Serverless SQL通过JDBC可以对接包括DMS、QuickBI、tableau等系统满足业务开发需求。
       方案三.png

    二、构建数据湖方案面临的挑战

    上面的两个数据湖方案是各大企业在阿里云上面通过实践沉淀下来的。还是有不少企业会问这两个数据湖方案确实很有价值,但是在落地的过程中总是会遇到一些问题,导致方案落地缓慢,比如:

    • 如何构建数据的统一管理视图:OSS不像数据库及数据仓库具有元数据管理系统,导致海量数据存储后难以管理。另外各个数据库、数据仓库等系统有自己的元数据,形成了数据孤岛,难以进行统一管理,释放数据联邦分析价值;
    • 如何构建多租户的权限管理:如果全域数据都使用数据湖方案管理,企业多部门研发人员共同使用数据湖挖掘价值,但是缺少有效的数据租户及权限隔离,产生数据风险;
    • 如何自动化的构建元数据:OSS上面的文件量巨大、且这些数据是动态增长变化的;如果手动创建元数据一方面效率低,同时无法满足动态更新的需求;
    • 如何简单的进行数据入湖:为了满足数据写入的实时性,比如日志场景数据的入口是在类似Kafka、Loghub等消息系统,这些数据怎么高效的归档到OSS进行后续的分析?存储在数据库中的数据以前为了节省成本,以及保证稳定性,通常会只保留最近一段时间的数据,在有数据湖方案后,想要把这些数据归档到数据湖做后续的分析,那么如何简单高效的归档到OSS数据湖呢?

    结合用户的这些挑战和痛点,阿里云数据湖分析服务DLA的数据湖管理功能可以有效的提高构建数据湖的效率,接下来一起把这些功能玩转起来吧

    三、DLA高效的数据湖管理功能

    阿里云数据湖分析服务DLA的数据湖管理功能定位为帮助用户构建统一、安全、高效、开放的数据湖解决方案。从下面的数据湖方案整体架构图可以看出:

    • 存储对接:数据湖管理向下管理好数据湖存储的数据,包括构建OSS目录的元数据系统以及方便的把流式数据及Database的数据归档到OSS管理起来;
    • 分析与计算支持:数据湖管理向上为多种数据湖计算引擎提供统一的元数据系统,目前支持数据湖原生分析与计算引擎DLA Serverless SQL(兼容Presto)、DLA Serverless Spark;部分Hadoop&Spark生态,比如Apache Hudi;AnalyticDB、MaxCompute、EMR等系统也可以对接数据湖管理的元数据系统。
      数据湖管理概括.png

    数据湖管理核心功能包括:元数据管理、元数据爬取、数据入湖、实时数据湖。下面一起来看下这些功能是如何高效的帮助构建数据湖的。

    3.1 元数据管理

    数据湖存储的数据量更加大、数据格式更加丰富,为了对这些数据进行安全的管理和挖掘价值,需要一套同时具备基本管理能力、多租户权限管理能力、扩展能力、开放能力的统一元数据系统。阿里云数据湖分析服务DLA的元数据系统具备这些能力。

    3.1.1 DLA元数据管理介绍

    下面是数据湖分析服务DLA的元数据管理系统的架构图:

    • 存储层:DLA元数据管理系统是一套多租户的服务,管理所有用户元数据,目前是每个regoin部署一套。智能数据路由层,用来做租户元数据的存储管理,能够根据用户元数据量级动态扩展调整;
    • 核心服务层:元数据管理系统提供database、table、partition的服务进行库、表、分区、列的管理能力,同时支持使用租户服务、权限服务、生命周期管理,权限粒度可以支持到库、表、列;
    • 接入层:统一元数据管理服务为了支持更多计算引擎对接,同时支持通过JDBC、阿里云OpenAPI来使用元数据服务;目前以JDBC为主,阿里云OpenAPI对接进行中。身份认证支持对接阿里云RAM账号体系,以及DLA账号体系。同时会有元数据相关的请求QPS监控。
    • 生态层:目前这套元数据管理支持对接云原生的数据湖分析引擎DLA Serverless SQL&Spark;开源Hadoop&Spark,其中Apache 顶级项目Hudi已经原生支持了阿里云数据湖分析元数据HUDI-841;阿里云数据库备份DMS也使用这套元数据作为其备份数据湖分析的元数据系统;AnalyticDB、EMR、MaxCompute等也可以进行对接。
      元数据管理介绍.png

    3.1.2 DLA元数据管理上手

    • 可视化全局管理视图
      如下图可以在阿里云数据湖分析DLA的控制台“元数据管理”进行元数据的操作,比如“创建Schema”、查看库表信息、查询数据等。

    元数据管理页面.png

    • 创建元数据

      • 自动化元数据创建:可以参考3.2元数据爬取、3.3数据入湖的详细介绍
      • SQL手动创建:支持HIVE风格的DDL语法
      • SQL自动创建:支持create table like mapping语法自动识别数据源文件的列,减少手写很多列及类型的麻烦;支持MSCK REPAIR DATABASE语法自动把Database下面的表创建好;支持MSCK REPAIR TABLE自动把table下面的分区和数据目录创建映射好。
    • 权限管理:目前支持通过JDBC进行权限的GRANT和REVOKE,通过阿里云OpenAPI也在研发中

      • GRANT:权限类型可以取值为SELECT、 SHOW、 ALTER、DROP、CREATE、INSERT、UPDATE、DELETE、GRANT OPTION、 ALL、ALL PRIVILEGES、USAGE等,通过这些类型进行用户的权限授权
      • REVOKE:和GRANT对称,可以进行用户权限的撤销授权。

    3.2 元数据爬取

    用户基于OSS进行数据湖存储时,数据具有规模大、格式丰富、动态变化、非结构化字段多的特点,这种情况下手动创建的可行性及成本会比较高。

    3.2.1 DLA元数据爬取介绍

    元数据爬取功能可以自动为OSS上面的数据文件创建及更新数据湖元数据,方便分析和计算。具有自动探索文件数据字段及类型、自动映射目录和分区、自动感知新增列及分区、自动对文件进行分组建表的能力。目前主要支持了自动爬取OSS上面的元数据,Database的自动元数据构建在开发中。核心功能包括:

    • 自动探索格式:企业存储在OSS上面的数据格式多种多样,比如常见的有json、csv、parquet、orc等,同时不同文件里面的字段数目及类型也是多种多样的。DLA元数据爬取功能具备自动探索这些schema的能力。
    • 增量发现:存储在OSS上面的数据是动态变化的,比如用户会向同一个目录下面持续的上传文件,且文件的字段数也会增加、Loghub投递到OSS上面的文件会增量的通过日期目录来写入等。元数据爬取功能能够覆盖这些场景;
    • 规模扩展:随着要爬取的OSS上面文件规模的增大,元数据爬取任务可以自动弹性伸缩资源来保证元数据爬取任务端到端的延迟。

    元数据爬取.png

    3.2.2 DLA元数据爬取10分钟上手

    使用DLA的元数据爬取可以通过DLA的控制台,同时元数据爬取开放阿里云OpenAPI也在研发中,同时也会被集成到其他云产品中。下面一起玩转一下元数据爬取功能:

    • 创建任务:左侧选择要爬取的具体OSS路径,右侧配置爬取的元数据要存储到DLA元数据系统的Schema名称即可,其他高级选项根据实际需求调整。

    元数据爬取创建.png

    • 任务管理:爬取任务会自动周期运行,可以通过这个界面管理任务的运行情况。支持查看任务的运行状态、配置的修改、跳转到DLA的SQL窗口进行快速的数据查询。

    元数据爬取管理.png

    3.3 数据入湖

    企业并不是所有业务数据直接存储在数据湖OSS中,其他的业务数据存储主要有两类包括消息中间件、Database,而这些数据都有归档存储到数据湖OSS中进行统一计算分析的需求。因此简单易用的数据入湖功能成为普遍的需求。

    3.3.1 DLA数据入湖介绍

    阿里云数据湖分析DLA的数据入湖支持Database的全量&增量&多库合并建入湖、支持消息中间件数据的实时入湖等能力。

    • Database一键建湖:主要支持全量、增量、多库合并三种模式,其中增量模式正在开发中;核心价值如下:

      • 丰富的数据源:包含OLTP的MySQL、SQLServer、POLARDB等,同时支持NoSQL的mongoDB等;
      • 自动感知源库结构变化:能够自动识别源库的增表、增加字段等同步更新到OSS数据湖;
      • 整库多表建湖:一键建湖能够自动同步数据库的整个Database下面所有表,而不需要每张表都去单独配置;
      • 分表&分库合并建湖:有些业务场景为了提高OLTP的查询性能及数据隔离性,经常会进行分库及分表。目前某用户使用该功能支持了8200+ SqlServer库合并到一个DLA的库里面,这样可以对分库的数据进行中心化统一分析。
      • 源库影响最小化:数据入湖通过动态调整源库连接数的方式、以及选择业务低峰期归档,最小化对源库的影响。

    一键建湖.png

    • 实时数据入湖:对于云kafka、Loghub等消息中间、数据库的CDC数据可以通过“实时数据入湖”方案构建数据湖。

    该方案基于DLA Serverless的Spark Streaming以及数据湖增量存储格式Apache HUDI来构建,通过HUDI增量写入OSS的数据,同时自动在DLA的元数据系统构建元数据。详细介绍可以参考文章,核心优势如下:    

    • 全链路数据延迟可达分钟级别,打造T + 0 数据湖;
    • 支持数据增量存储在OSS,支持Upsert/Delete,同时自动构建元数据管理;
    • 丰富的数据源,支持阿里云上超过95%数据源;
    • 一份数据存储在OSS,通过DLA Meta增量管理,降低存储成本低;

    实时数据湖.png

    3.3.2 Database一键建湖

    使用一键建湖可以通过DLA的控制台,同时可以通过数据管理DMS进行。下面主要介绍DLA控制台的使用,关于DMS使用一键建湖可以参考视频

    • 创建一键建湖:左侧选择数据源,可以包括RDS、PolarDB、MongoDB、ECS自建数据库;右侧配置源库的验证信息,以及在DLA生成的元数据名称即可。

    一键建湖创建.png

    • 任务管理:对于周期运行的建湖任务可以进行全局的管理,以及对建好的湖进行分析。

    数据入湖管理.png

    四、展望与总结

    数据湖分析DLA 是 Serverless的架构,支持 【按需与保留】 资源使用,打造最具性价比的数据湖分析平台;
    提供一站式的数据湖分析与计算服务,支持 ETL、机器学习、流、交互式分析,可以与OSS、数据库等多种数据源搭配使用;功能包括:数据入湖,元数据管理与自动发现,支持双引擎:【SQL(兼容Presto)分析、Spark计算服务】。其中数据湖管理这块会朝着更易用、更开放、更可靠方向迭代。
    注:数据湖管理控制台使用链接,数据湖管理及DLA的帮助文档
                   二维码.png

    ]]>
    DataWorks百问百答54:设置了数据质量校验,但是未触发校验怎么办?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    本周为大家解析设置了数据质量校验,但是未处触发校验的情况

    触发校验前提:

    DQC必须要在调度触发任务,并且对表数据有修改之后才可以触发,例如:
    1、insert overwrite table xxxxxxx
    2、create table as select xxxxxxx

    设置了校验,也有对表数据修改的操作,但未触发校验的日志表现情况:

    image.png
    触发了DQC Hook,但实际并没有进行数据质量校验的情况,是未触发实际的校验,一般有以下几种情况:

    1、如果是SQL级别的校验,在触发时,日志中打印的业务日期set SKYNET_BIZDATE=20190826;必须和insert的分区相符,才可以触发。类似如下截图所示,业务日期和触发分区不符,必须改成任务级别才可以。

    2、用户在分区表达式中设置了对当前时间前一天的校验,但在运行日志中,校验的又是当前时间的分区,会出现匹配不上分区的情况,不会触发规则校验,这时候,需要把分区表达式中改为对当前时间的校验,即:ds=${yyyymmdd}/xxxxxxxx/xxxx/xxx…… 
    下图中的另一个情况是:用户设置了多级分区,但日志中请求DQC的参数数据显示的分区顺序,没有和设置的分区表达式中的分区顺序匹配上,这也是一种不会触发校验的情况。
    image.png
    image.png

    3、odps表有二级分区,但在规则配置的分区表达式中,只设置了一级分区表达式,同样也会因为没有匹配上分区,而不触发校验。

    分区表达式

    分区表达式一定要写到最小粒度,而DQC支持的最小粒度为天。例如:我odps表有二级分区,ds=yyyymmdd hh=hh24,那么在写分区表达式时,一定要指定到二级分区hh,否则无法触发校验。
    其中弹内需要使用正则表达式来编写,弹内、公有云多级分区表达式如下所示:

    弹内多级分区写法:ds=${yyyymmdd-1}/hh=<[a-zA-Z0-9_-]*>
    公有云多级分区写法:ds=$[yyyymmdd]/hh=$[hh24]
    注意:弹内分区表达式符号使用“{}”,公有云符号使用“[]”。

    总结:

    触发数据质量规则校验,必须要对odps表数据有修改(insert、create)之后、表的分区也和规则设置的分区表达式匹配上方可触发哦。

    DataWorks百问百答历史记录 请点击这里查看>>

    更多DataWorks技术和产品信息,欢迎加入【DataWorks钉钉交流群】

    ]]>
    测试面试题集锦(六)| 软素质篇与反问面试官篇(附答案)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    本文为霍格沃兹测试学院学员学习笔记。

    本系列文章总结归纳了一些软件测试工程师常见的面试题,主要来源于个人面试遇到的、网络搜集(完善)、工作日常讨论等,分为以下几个部分,供大家参考。如有错误的地方,欢迎指正。有更多的面试题或面试中遇到的坑,也欢迎补充分享。希望大家都能找到满意的工作,共勉之!~

    软件测试工程师面试题集锦

    1. 测试常见问题与流程篇
    2. 测试工具篇
    3. 计算机网络知识与数据库篇
    4. Linux 与 Python 编程技能篇
    5. 自动化测试与性能测试篇
    6. 软素质篇(10 大灵魂拷问)与反问面试官篇

    软素质篇(10 大灵魂拷问)

    1. 说一下自己的优点和缺点?
    • 避开岗位的核心技能
    • 把缺点放在场景中描述
    • 对缺点进行合理化解释
    • 优点随便说,主要方向还是在岗位上
    2. 是否能接受加班?(建议分情况作答)
    • 第一种情况:假设公司有重要的项目要赶。示范回答:贵公司现在正处于发展上升期,也在官网上有看到公司的重要项目成果,我觉得有时候因为赶项目进度、工作需要等忙起来是非常正常的,面对这种情况,我是非常愿意配合公司和团队的工作,让工作能够更顺利地完成,此外,我也相信自己一定能在公司安排的工作中获得到锻炼,获得更快地成长。
    • 第二种情况:假设自己作为新人,对业务不熟悉。示范回答:我作为公司刚进去的新人,可能刚开始进入公司接触业务时不太熟练,会出现需要加班的情况,但我更愿意提高工作效率,并积极向公司的前辈请教学习,在一定的时间内完成工作而不是拖到下班之后。当然, 如果有紧急的事情,忙起来需要加班也是可以接受的。
    3. 你对薪酬的要求?
    • 我上家公司基本在A~B 之间(建议合理提高,避免部分HR压价)。
    • 薪资并不是我求职的唯一标准,我来贵司求职的主要动机是兴趣,这份工作是我喜欢做的,也相信自己可以胜任,更相信公司会给出一个合理的薪酬。
    • 相比薪酬,我更在意的是收入,所以,我很愿意了解贵司的薪酬架构,可以简单介绍下吗?
    • 我希望薪资可以达到 XX,据我了解,贵司这个岗位薪资范围是A~B ,而结合岗位职责及任职要求,我对自己也进行了相应评估,也愿意接受贵司的下一步考核。
    4. 未来 5 年的职业规划
    • 自我认知。对自己是否了解,了解是不是靠谱。
    • 动机和价值观。你是否能接受我们并不一定能给你公平的职业发展机会这个现实?
    • 组织承诺。你到底能在我们这踏实的干几年?
    5. 为什么你觉得这个岗位适合自己?(为什么要聘用你)
    • 描述应聘岗位的胜任条件,强调自己的工作能力和工作经验跟岗位的匹配度,岗位要求的工作技能是否自己掌握了,掌握的程度是怎样的,最好在面试中说出来。因此,在面试前最好是要针对应聘岗位,把自己胜任的条件一一列出来,做到知己知彼。可以谈论一下自己之前的工作情况,用成绩、用数据来说明自己的成就。
    • 描述自己能为公司做出什么贡献,公司是一个讲究利益的地方,聘用你肯定要你为公司做出贡献。那么你在回答这个问题时,就需要说出你的加入可以为公司带来什么,这非常重要。因此,一定要明确你的工作目标和职业规划,表明你的立场和专业程度,让HR信任你。
    • 描述出自身的优势。公司为何要聘用你,而不聘用别人,肯定是你有比别人优秀的地方。那么在回答这个问题时,就一定要说出自己与众不同的地方,最好是要举一个例子,来支持你的观点。
    • 建立个人和公司的联系,HR想要得到一个怎样的答案呢?无非就是想通过这个问题,来进一步了解你各方面的信息,以及看看你为这次面试做了多少功课。那么在面试前,你最好是要尽可能获取有关公司可行业的资料信息。在回答的时候,结合自己所做的功课,建立个人和公司的联系,说明自己在哪一方面能够匹配公司的要求。HR看到你对应聘岗位这么了解,肯定会对你有好感。
    • 说出你对这份工作的兴趣以及热情。
    6. 对我们公司有多少了解?
    • 如果不了解,就按实际情况回答就好,知道多少就说多少,(很多时候去面试对这个公司的了解都是从网上查到的,不会太深入);
    • 但最好是提前做好一些调研和准备工作;
    7. 为什么愿意到我们公司?
    • 有所准备,了解公司基本情况
    • 个人目标与公司目标一致
    • 强调你能如何为公司创造价值
    8.与领导/团队同事意见不一致时,该如何处理?
    • 不要假设“我已经完全的掌握了对这件事的认知”。向领导询问确认自己有可能缺失的信息。要寻找对领导没有告知的信息,和领导不能透露的信息。
    • 不要假设领导已经完全的掌握了我对这件事的认知。检查一下,是否已经将事情的前因后果,自己对事情的理解,明确清晰的传达给了领导,以及,他是否真的已经明确了解。
    • 在进行有效的认知沟通后,重新思考整件事情。如果意见还是有不一致,那么:

      • 按领导要求执行。不理解,也执行,在执行中理解。
      • 执行过程中,收集反馈,不断调整,提升认知。
      • 执行完成后,及时复盘,回顾决策和行动过程,沉淀知识。
    9. 缺乏工作经验,如何胜任这份工作?
    • 承认工作经验的重要性。这样能带给面试官的印象是:该位候选人认知能力较强,具有理性思维与客观公正的处事态度及判断能力,尤其是对于自己也能客观公正地看待,勇于承认自己的缺失。
    • 突显个人优势。用自己的其他优势特长来补足经验上的不足,比如说记忆力好、动手能力强、语言能力强、学习能力强等。
    • 强调自己会不断提高工作能力。切忌用假大空的话,要用具体的与工作相关的事例或是数据来说明自己的学习力。
    10. 工作/会议中与同事发生争执,如何处理?
    • 在沟通之前,做好充分的准备
    • 学会认真倾听,让别人把话说完
    • 发现有情绪化苗头时,及时停止会议
    • 借用一些工具,来解决交流障碍
    • 学会非暴力沟通的方式

    反问面试官

    1.职责
    • 团队中初级和高级人员如何平衡
    • 针对员工有哪些培训和提升计划
    2.技术
    • 公司内部的技术栈
    • 产品的架构
    • 版本控制及迭代速度
    • 服务器管理权限,本地计算机管理权限
    3.团队
    • 团队内和团队之间如何沟通
    • 遇到了分歧如何解决
    • 团队正在经历的尚未解决的挑战是什么
    • 绩效考核是如何算的
    4. 公司
    • 晋升机会
    • 是否有自己的学习资源
    • 假期,加班工资等
    • 过去半年最糟糕的一天是怎么样的
    • 是什么让你来到并留在这里
    • 是否能够平衡工作与生活

    以上,本文是测试面试题集锦系列的完结篇。后续我们还将分享更多大厂面试真题,欢迎大家关注

    免费领取:接口测试+性能测试+自动化测试+测试开发+测试用例+简历模板+测试文档

    ]]>
    超详细的canal入门,看这篇就够了-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 思维导图

    在这里插入图片描述

    本文章已收录到个人博客网站(我爱B站):me.lovebilibili.com

    前言

    我们都知道一个系统最重要的是数据,数据是保存在数据库里。但是很多时候不单止要保存在数据库中,还要同步保存到Elastic Search、HBase、Redis等等。

    这时我注意到阿里开源的框架Canal,他可以很方便地同步数据库的增量数据到其他的存储应用。所以在这里总结一下,分享给各位读者参考~

    一、什么是canal

    我们先看官网的介绍

    canal,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

    这句介绍有几个关键字:增量日志,增量数据订阅和消费

    这里我们可以简单地把canal理解为一个用来同步增量数据的一个工具

    接下来我们看一张官网提供的示意图:

    canal的工作原理就是把自己伪装成MySQL slave,模拟MySQL slave的交互协议向MySQL Mater发送 dump协议,MySQL mater收到canal发送过来的dump请求,开始推送binary log给canal,然后canal解析binary log,再发送到存储目的地,比如MySQL,Kafka,Elastic Search等等。

    二、canal能做什么

    以下参考canal官网

    与其问canal能做什么,不如说数据同步有什么作用。

    但是canal的数据同步不是全量的,而是增量。基于binary log增量订阅和消费,canal可以做:

    • 数据库镜像
    • 数据库实时备份
    • 索引构建和实时维护
    • 业务cache(缓存)刷新
    • 带业务逻辑的增量数据处理

    三、如何搭建canal

    3.1 首先有一个MySQL服务器

    当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

    我的Linux服务器安装的MySQL服务器是5.7版本。

    MySQL的安装这里就不演示了,比较简单,网上也有很多教程。

    然后在MySQL中需要创建一个用户,并授权:

    -- 使用命令登录:mysql -u root -p
    -- 创建用户 用户名:canal 密码:Canal@123456
    create user 'canal'@'%' identified by 'Canal@123456';
    -- 授权 *.*表示所有库
    grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'canal'@'%' identified by 'Canal@123456';

    下一步在MySQL配置文件my.cnf设置如下信息:

    [mysqld]
    # 打开binlog
    log-bin=mysql-bin
    # 选择ROW(行)模式
    binlog-format=ROW
    # 配置MySQL replaction需要定义,不要和canal的slaveId重复
    server_id=1

    改了配置文件之后,重启MySQL,使用命令查看是否打开binlog模式:

    在这里插入图片描述

    查看binlog日志文件列表:

    在这里插入图片描述

    查看当前正在写入的binlog文件:

    在这里插入图片描述

    MySQL服务器这边就搞定了,很简单。

    3.2 安装canal

    去官网下载页面进行下载:https://github.com/alibaba/canal/releases

    我这里下载的是1.1.4的版本:

    在这里插入图片描述

    解压canal.deployer-1.1.4.tar.gz,我们可以看到里面有四个文件夹:

    接着打开配置文件conf/example/instance.properties,配置信息如下:

    ## mysql serverId , v1.0.26+ will autoGen
    ## v1.0.26版本后会自动生成slaveId,所以可以不用配置
    # canal.instance.mysql.slaveId=0
    
    # 数据库地址
    canal.instance.master.address=127.0.0.1:3306
    # binlog日志名称
    canal.instance.master.journal.name=mysql-bin.000001
    # mysql主库链接时起始的binlog偏移量
    canal.instance.master.position=154
    # mysql主库链接时起始的binlog的时间戳
    canal.instance.master.timestamp=
    canal.instance.master.gtid=
    
    # username/password
    # 在MySQL服务器授权的账号密码
    canal.instance.dbUsername=canal
    canal.instance.dbPassword=Canal@123456
    # 字符集
    canal.instance.connectionCharset = UTF-8
    # enable druid Decrypt database password
    canal.instance.enableDruid=false
    
    # table regex .*\..*表示监听所有表 也可以写具体的表名,用,隔开
    canal.instance.filter.regex=.*\..*
    # mysql 数据解析表的黑名单,多个表用,隔开
    canal.instance.filter.black.regex=

    我这里用的是win10系统,所以在bin目录下找到startup.bat启动:

    启动就报错,坑呀:

    要修改一下启动的脚本startup.bat:

    在这里插入图片描述

    然后再启动脚本:

    在这里插入图片描述

    这就启动成功了。

    Java客户端操作

    首先引入maven依赖:

    <dependency>
        <groupId>com.alibaba.otter</groupId>
        <artifactId>canal.client</artifactId>
        <version>1.1.4</version>
    </dependency>

    然后创建一个canal项目,使用SpringBoot构建,如图所示:

    在这里插入图片描述

    在CannalClient类使用Spring Bean的生命周期函数afterPropertiesSet():

    @Component
    public class CannalClient implements InitializingBean {
    
        private final static int BATCH_SIZE = 1000;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // 创建链接
            CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");
            try {
                //打开连接
                connector.connect();
                //订阅数据库表,全部表
                connector.subscribe(".*\..*");
                //回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿
                connector.rollback();
                while (true) {
                    // 获取指定数量的数据
                    Message message = connector.getWithoutAck(BATCH_SIZE);
                    //获取批量ID
                    long batchId = message.getId();
                    //获取批量的数量
                    int size = message.getEntries().size();
                    //如果没有数据
                    if (batchId == -1 || size == 0) {
                        try {
                            //线程休眠2秒
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        //如果有数据,处理数据
                        printEntry(message.getEntries());
                    }
                    //进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。
                    connector.ack(batchId);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connector.disconnect();
            }
        }
    
        /**
         * 打印canal server解析binlog获得的实体类信息
         */
        private static void printEntry(List<Entry> entrys) {
            for (Entry entry : entrys) {
                if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                    //开启/关闭事务的实体类型,跳过
                    continue;
                }
                //RowChange对象,包含了一行数据变化的所有特征
                //比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等
                RowChange rowChage;
                try {
                    rowChage = RowChange.parseFrom(entry.getStoreValue());
                } catch (Exception e) {
                    throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e);
                }
                //获取操作类型:insert/update/delete类型
                EventType eventType = rowChage.getEventType();
                //打印Header信息
                System.out.println(String.format("================》; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                        entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                        entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                        eventType));
                //判断是否是DDL语句
                if (rowChage.getIsDdl()) {
                    System.out.println("================》;isDdl: true,sql:" + rowChage.getSql());
                }
                //获取RowChange对象里的每一行数据,打印出来
                for (RowData rowData : rowChage.getRowDatasList()) {
                    //如果是删除语句
                    if (eventType == EventType.DELETE) {
                        printColumn(rowData.getBeforeColumnsList());
                        //如果是新增语句
                    } else if (eventType == EventType.INSERT) {
                        printColumn(rowData.getAfterColumnsList());
                        //如果是更新的语句
                    } else {
                        //变更前的数据
                        System.out.println("------->; before");
                        printColumn(rowData.getBeforeColumnsList());
                        //变更后的数据
                        System.out.println("------->; after");
                        printColumn(rowData.getAfterColumnsList());
                    }
                }
            }
        }
    
        private static void printColumn(List<Column> columns) {
            for (Column column : columns) {
                System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            }
        }
    }

    以上就完成了Java客户端的代码。这里不做具体的处理,仅仅是打印,先有个直观的感受。

    最后我们开始测试,首先启动MySQL、Canal Server,还有刚刚写的Spring Boot项目。然后创建表:

    CREATE TABLE `tb_commodity_info` (
      `id` varchar(32) NOT NULL,
      `commodity_name` varchar(512) DEFAULT NULL COMMENT '商品名称',
      `commodity_price` varchar(36) DEFAULT '0' COMMENT '商品价格',
      `number` int(10) DEFAULT '0' COMMENT '商品数量',
      `description` varchar(2048) DEFAULT '' COMMENT '商品描述',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

    然后我们在控制台就可以看到如下信息:

    如果新增一条数据到表中:

    INSERT INTO tb_commodity_info VALUES('3e71a81fd80711eaaed600163e046cc3','叉烧包','3.99',3,'又大又香的叉烧包,老人小孩都喜欢');

    控制台可以看到如下信息:

    在这里插入图片描述

    总结

    canal的好处在于对业务代码没有侵入,因为是基于监听binlog日志去进行同步数据的。实时性也能做到准实时,其实是很多企业一种比较常见的数据同步的方案。

    通过上面的学习之后,我们应该都明白canal是什么,它的原理,还有用法。实际上这仅仅只是入门,因为实际项目中我们不是这样玩的...

    实际项目我们是配置MQ模式,配合RocketMQ或者Kafka,canal会把数据发送到MQ的topic中,然后通过消息队列的消费者进行处理

    Canal的部署也是支持集群的,需要配合ZooKeeper进行集群管理。

    Canal还有一个简单的Web管理界面。

    下一篇就讲一下集群部署Canal,配合使用Kafka,同步数据到Redis

    参考资料:Canal官网

    絮叨

    上面所有例子的代码都上传Github了:

    https://github.com/yehongzhi/mall

    如果你觉得这篇文章对你有用,点个赞吧~

    你的点赞是我创作的最大动力~

    想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!

    在这里插入图片描述

    能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

    ]]>
    高并发系统三大利器之限流-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 什么是限流?

    限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。

    坐地铁上班的同学对于这张图片是不是都不会陌生。
    在这里插入图片描述
    基本上在上下班的早晚高峰我们就会发现进站的闸机会有一部分是关闭的。为什么地铁站会关闭一部分闸机呢?这就是为了限流。毕竟地铁站就那么大,可容纳的人数也就那么多。如果大家一股脑全部挤进地铁站是不是又会发生踩踏事件什么的。这是生活中的限流。还有我们去景区玩,景区的门票是不是也是固定的,每天就卖那么多张,卖完即止。限流是不是和我们的生活也息息相关。

    为什么要限流?

    开篇也有说到限流是为了保证系统的稳定运行。假设我们一个系统一小时之最多只能处理10000个请求,但是一小时流量突增10倍,这突增的流量我们如果不进行限制的话,任由它直接进入系统的话,是不是直接会把我们的系统弄瘫痪,就无法对外提供服务了。本人就曾经被这个所坑过,有一次把爬虫开关拦截的开关给关掉了,突然有一大波的爬虫流量进入系统中,我们也没有把这些爬虫请求进行拦截,然后一股脑的全部给转发到下游系统里面去了。下游系统直接就找上门来了,造成他们的服务发生大量的超时。比如地铁早高峰的时候我们如果不对地铁站进行限流的话,大家是不是都会往地铁站挤,然后再往地铁里面挤,挤不上都还要挤。会导致地铁门都关不上,然后地铁就开不走,会导致越来越多的人堵在地铁站。然后最后就会导致整条地铁线都阻塞了。上班就妥妥的迟到了(对于程序员说大多数应该是弹性制的所以也不存在迟到这一说法)。

    限流操作有哪些?

    拒绝服务

    这个是最最简单粗暴的做法了,直接把请求直接拒绝掉。
    比如早高峰坐地铁的时候,直接让进入1000个人,剩下多出来的人不让坐地铁了。直接把入站口给关闭了。

    服务降级

    将系统的所有功能服务进行一个分级,当系统出现问题,需要紧急限流时,可将不是那么重要的功能进行降级处理,停止服务,这样可以释放出更多的资源供给核心功能的去用。
    假设有一个功能新用户注册完,要给用户发送多少优惠券。这时候服务降级的话就可以直接把送券服务关掉,让服务快速响应,提高系统处理能力。
    应用到早高峰坐地铁的时候比如在人民广场这个大站点,处理不过来了那么多人换乘,我们是不是可以直接地铁一号线在人民广场不停,直接到下一站在停,这时候经过人民广场换乘的人就少了。

    延迟处理

    把请求全部放入到队列中,真正处理的话,就从队列里面依次去取,这样的话流量比较大的情况可能会导致处理不及时,会有一定的延时。双十一零点我们付款的时候,去查询订单的状态是不是也会有一定的延时,不像在平时付完款订单状态就变成了付款状态。

    特权处理

    这个模式需要将用户进行分类,通过预设的分类,让系统优先处理需要高保障的用户群体,其它用户群的请求就会延迟处理或者直接不处理。我们去银行办理业务的时候是不是也会经常需要排队,但是是不是经常会VIP用户、什么白金卡用户,直接不需要排队,直接一上来就可以办理业务,还优先处理这些人的业务。是不是特羡慕这些人,哎 羡慕也没办法谁叫人家有钱咧。

    限流的实现方式?

    计数器方法

    这是最简单的限流算法了,系统里面维护一个计数器,来一个请求就加1,请求处理完成就减1,当计数器大于指定的阈值,就拒绝新的请求。是通过全局的总求数于设置的阈值来达到限流的目的。通常应用在池化技术上面比如:数据库连接池、线程池等中应用。这种方式的话限流不是平均速率的。扛不住突增的流量。

    漏桶算法

    在这里插入图片描述
    我们可以看到水是可以持续流入漏桶里面的,底部也是匀速的流出,如果流入的速率大于底部流出的速率,以及漏桶的水超过桶的大小就会发生益出。请求一经过漏桶的过滤,不管你请求有多少,速率有多快,我反正就这么个速度处理。我们平时坐地铁的时候是不是也是这样,不管你乘客有多少,反正就是隔5min发一趟车。那早高峰的时候你5min钟一趟车根本就不够用啊,上班的人太多啊,你需要加快速度处理啊,所以可能早高峰改为3min一趟,动态调整速率。

    令牌桶

    在这里插入图片描述

    看图的话是不是令牌桶和漏桶都差不多,只不过令牌桶新增了一个匀速生产令牌的中间人以恒定的速度往桶里面放令牌,如果令牌的数量超过里桶的限制的话,令牌就会溢出,这时候就直接舍弃多余的令牌。每个请求过来必须拿到桶里面拿到了令牌才允许请求(拿令牌的速度是不限制的,这就意味着如果瞬间有大量的流量请求进来,可以短时间内拿到大量的令牌),拿不到令牌的话直接拒绝。这个令牌桶的思想是不是跟我们java里面的Semaphore 有点类似。Semaphore 是拿信号量,用完了就还回去。但是令牌桶的话,不需要还回去,因为令牌会定时的补充。令牌桶算法我们可以通过Google开源的guava包创建一个令牌桶算法的限流器。

    总结

    • 以上粗略的介绍了几种单机的限流思想,大家可以根据这个思想然后去实现各种各样的限流组件。
    • 我们的限流算法每个里面是不是都一个阈值,这个阈值设置为多少是不是比较难。
      阈值设置过大的话,服务可能扛不住,阈值设置小了会把用户请求给误杀,资源没有得到最大的一个利用。
    • 分布式限流的话,以后有机会再讲。

    结束

    • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
    • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
    • 感谢您的阅读,十分欢迎并感谢您的关注。
      1590211107925.gif
    ]]>
    如何导入本地镜像到阿里云ECS服务器-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 镜像导入的使用场景
    在操作之前,我们来介绍一下镜像导入的使用场景。
    一种情况是,我们需要把线下环境整体搬到云上,这种情况可能有以下特征。
    1.线下预部署好的服务器
    2.需要在线下进行测试和调试的服务
    3.应用需要在线下激活且允许移动的服务
    4.线下老旧的物理服务器需要上云
    5.另一种情况是,阿里云当前没有可用的操作系统类型供选择。比如说,操作系统类型没有、操作系统版本已经下线、线下定制的操作系统。

    阿里云镜像导入的限制
    不同于本地服务器,阿里云服务器ECS的镜像导入还是有一些限制的。
    image.png

    对于Linux系统,我们在导入镜像之前可以使用阿里云提供的镜像规范检测工具,检测各项配置指标是否合规。
    检测命令如下:
    wget http://docs-aliyun.cn-hangzhou.OSS.aliyun-inc.com/assets/attach/73848/cn_zh/1557459863884/image_check

    chmod +x image_check

    sudo <检测工具所在路径>/image_check –p [目标路径]

    sudo ./image_check
    参考资料:阿里云帮助中心-镜像规范检测工具

    导入镜像步骤
    首先打开OSS的控制台选择一个bucket,选择上传文件,上传已经本地已经做好的镜像,支持RAW和VHD格式。
    image.png

    这里我们已经上传了一个镜像,点击获取地址。
    image.png

    在弹窗中点击获取并复制生成的地址。
    image.png

    注意镜像文件上传的OSS区域,要和ECS同区域。接下来,我们进入ECS服务器控制台,进入镜像栏目,然后点击导入镜像。
    image.png

    这里要注意,要先确认ecs官方服务账号可以访问对应的OSS权限。
    image.png

    如果没有授权,点击授权即可。
    image.png

    授权后重现回到导入镜像界面。
    镜像所在地:选择和OSS同一地域
    OSS Object地址:填写刚才生成的OSS文件访问地址
    镜像名称:我们这里填demo5
    操作系统:我们选择Linux
    系统盘大小:注意这个大小不是指镜像文件的大小,而是我们创建这个镜像时所占用的磁盘空间大小,我们这里填40
    系统架构:选择对应的架构,我们这里是X86_64
    系统平台:centOS
    镜像格式:可选RAW和VHD,我们这里选择VHD

    填写完成后,点击确认。
    image.png

    这样我们就创建成功了,对应的镜像正在创建中,创建完成后,我们就可以用这个镜像部署服务器环境了。您掌握了吗?
    原文地址

    ]]>
    Midway Serverless 能力介绍与设计分析-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    作者 | 张挺

    这次分享的内容分为两部分, 一块是 Midway Serverless 的能力介绍,第二块是这些能力的设计、思考、沉淀。
    image.png

    Background

    image.png
    当前业界的 Serverless 化方向如火突入,有如阿里正在利用 Serverless 将原有业务迁移,降低成本的,也有正在向这些方向努力前进的,我首先会介绍一下当前 Serverless 的一些背景,前端使用 Serverless 的一些场景和方向。第二块会简单介绍一下 Midway Serverless 的一些基础上手和使用。第三块会介绍 Midway Serverless 在抹平平台差异,架构防腐层的一些设计与心得,最后是对未来的一些期望,方向的思考。

    Midway 从 2014 年开始一直在集团承担 Node.js 应用的基础开发框架,最开始是 express,到后面的 koa,egg 体系等,将集团业务承载至今。最开始的前后端分离,到如今的函数化,都在不断的开拓前端职能,让业务更聚焦,开发更提效。

    之前的 midway v1 版本,我们认为 midway 是一个 Web 全栈框架,提供 Web 服务,增加了依赖注入之后,也适合于大型应用的开发,灵活性和应用的可维护性也得到了验证。

    而到了现在 Midway Serverless 时期,整个 Midway 框架的定位在逐步的变化。
    image.png
    首先,Midway Serverless 是一个 serverless framework,可以在让代码在多云平台部署,在用户选择时可以减少一些顾虑。

    第二是能够方便的让传统的应用迁移上现有的弹性服务,毕竟在集团内,还有非常多的传统应用,不管是在什么场景,这些应用都还需要人维护,需要占用大量的资源,如果能上弹性,对节省成本有非常大的好处。

    第三是让应用本身能够在传统应用和函数之间切换,传统的 midway 是基于装饰器加上依赖注入的特性构建出来的,在函数体系下上,也可以这样做,甚至于通过构建将不同的场景结合到一起,我们希望最后能达到代码不变的情况下,不同场景都可用的状态。

    结构

    image.png
    这是我们经典的目录结构,最简单的,抛开 ts 的一些文件,只需要 f.yml 这个配置以及 index.ts 这个逻辑文件,而复杂一点,也只是增加了目录,增加了不同逻辑的分层,和传统的写法契合。

    这里的 f.yml 就承载了之前的路由层的功能,在 Serverless(FaaS)体系中,路由交给了网关处理,那么我们只需要在项目代码中写对应的原 Controller 的内容即可。
    image.png
    如图所示,f.yml 中每一个服务都会对应一个接口,每个接口都由一个方法承载由 handler字段去映射绑定,而实际运行中,通过依赖注入的方式,框架只根据当前执行的逻辑动态初始化其中方法,所以也不需要担心执行的性能问题。
    image.png
    f.yml 通过标准化适配多云平台,最简单的来说,可以通过定义 http 触发器的 path 和 method 具体的指定接口地址,也可以简化到默认值,自动变为通配路由等等。
    image.png

    工具链和能力

    除了 f.yml 这套标准定义文件,我们还提供了 faas-cli ,一个精简的本地开发工具,帮助函数体系开发的更好。在开发层面,我们只精简的提供了 create,invoke,test,deploy 四个命令,对应了整个研发流程的四个周期,而剩下的部分,则交给了对应平台自身的能力来完成,同时,我们后续也会提供一些后置管理,让 Node.js 开发本身更加的高效。

    从 v1.0 之后,我们也提供了一系列示例,不管是和前端集成的 React/Vue,还是场景化示例,博客,Todo list 等等。
    image.png

    原理解析

    虽然给大家展示了开发的工具链,开发的标准,解释了运行时机制,大家是不是还是很疑惑,依赖注入是如何把 f.yml 中的 handler 字段如何与代码中对应的装饰器连接的,而函数整个原来的参数是如何和云平台对接,做到一套代码跨多平台的?

    为了方便理解,我们拿 Midway v1 里的依赖注入容器来解释。
    image.png
    整个 Midway v1 是基于 EggJS 往上扩展,增加 IoC(依赖注入) 容器的初始化部分,并且将装饰器的能力注册到其中,和整个路由体系结合到一起。

    右边是我们核心的伪代码,在初始化时,容器会做一次扫描,把当前用户的代码都加载到内存中,并分析其中的装饰器组成一个”依赖图“,在每次执行逻辑的时候,从其中拿到对应的实例(get),并将其依赖,子依赖统一初始化。
    image.png
    路由部分也是这种逻辑的其中一层,在调用路由时,获取到对应的 Controller key,找到对应的方法,整个 Midway v1 都是如此运行起来的。

    在之后的迭代过程中,我们发现这样和单一框架依赖会比较深,很难去灵活的调整功能,并且在 Web 场景的能力,很难去适配到其他场景,这就给逻辑的复用和扩展造成了不少困难。

    我们希望不同的场景的代码,能够在一定程度上能够复用,比如常见的 router/orm/graphql 等等,都是可以横跨不同的场景去复用的,甚至于用户的服务层代码本身也是可以去多处复用的。另外一块,我们希望传统全栈到 Serverless 的过程是有延续性的,不希望代码的写法有比较大的区别,既能在不同的平台通用,又能在不同的技术栈大部分通用。

    这也迫使我们的去思考不同的代码设计,找到最佳的路径。

    Design

    image.png
    我们从最原始的函数写法给大家讲起。

    架构防腐

    整个原始的入口函数,社区的写法都非常简单,是一个传统的方法,其参数在不同平台根据不同触发器略有不同。
    image.png
    在执行时,通过网关调度到其内部的运行时,然后拼装参数执行到用户的入口函数中。

    为了和之前的框架结合,以及屏蔽不同平台之间的差异,我们在社区的运行时执行之后,用户入口函数之前,做了一层架构抽象,即我们所谓的”防腐层“。这层一共包括两个功能,一是运行时防腐的部分,屏蔽出入参数差异,屏蔽异步差异,错误处理等等。第二部分是 API 传承,将传统的 Midway v1 的容器初始化,根据 yml 里的信息实例化对应的函数方法。
    image.png
    这样说有一点抽象,我们找一段实际的代码来理解一下。

    下面是实际生成的代码入口 index.js 的示例。
    image.png
    初始化的时候,我们会做两个事情,一个就是每个平台的适配器,会自动根据 f.yml 中配置的 provider.name 来生成,我们会自动提供支持的平台启动器(现在已经有阿里云,腾讯云,以及即将完成的 aws)。

    另外一个就是 Midway Serverless 框架的入口(FaaSStarter),通过它,来调用到实际的用户代码(src/index.ts)。其余的 asyncWrapper 和 asyncEvent 则是用于对异步函数的包裹,让代码可以统一用上 async 关键字。
    image.png
    看到这里,大家是否好奇我们的运行时适配器(防腐层)内部是如何运作的?这就来稍微详细一点的讲讲。
    image.png
    整个运行时处在最中间的部分,往上承接事件带来的数据,接收,中转,往下调用到业务代码,把中转的参数传入,在整个容器中占了非常重要的部分 。一般来说,整个运行时包括几个部分,一是语言的 VM,基建的 SDK 等等,比如 Node.js 10/12,日志采集模块等等,二是运维的脚本,用来控制启停,健康检查等等,第三块就是运行时实际代码,简单的实现的话,可以理解起了个 http 服务,并在其中加载业务函数的代码。
    image.png

    生命周期

    传统的社区平台都会默认埋入自己的运行时,而我们的运行时则是在这些平台内置的运行时之上的封装,并且将运行时和业务代码通过自定义生命周期进行关联,将整个代码 run 起来。
    image.png
    整个生命周期分为几部分阶段,外围运行时包括 RuntimeStart、FunctionStart、Invoke、Close 等阶段,而在这些周期中,还提供了 before 和 after 的钩子,方便对这些阶段进行扩展。

    我们来一下实际的运行时扩展的例子,看看我们是如何抹平不同的云平台的。
    image.png
    这是一个阿里云运行时适配器的例子,我们接着上面的业务代码调用的路径来观察,asyncEvent 用来包裹真实的入参,在接受到参数之后,我们做了一些不同触发器的类型判断,将其分为了 Web 和非 Web 两种类型。
    image.png
    在 Web 的处理方法中,进一步细化内容,比如判断是否是网关的类型,构造出一个类似 koa ctx 的结构,处理 body 参数等。
    image.png
    做完这些事情之后,就开始把规则化好的参数传递给用户的真正的逻辑了,这个时候,由于生命周期的存在,开始执行才看到的 invoke 过程,并在内部调用 before 和 after 过程。

    除此之外,我们还提供了一个默认执行拦截的能力,这个能力在传统应用迁移的时候起了巨大的作用。

    应用迁移

    image.png
    现在,所有的扩展(Layer)都可以复用在所有的运行时适配上,整个函数体系和应用迁移体系基于这套生命周期和扩展机制,将能力复用,结构分层表现的淋漓尽致。
    image.png
    而用户所需要做的,仅仅增加一个 f.yml,写入这 4 行即可。整个 midway 构建器 会生成所有需要的入口和适配代码。

    这套函数和应用统一的方案,如果在企业内部,私有化部署也是非常适合的,阿里集团内部的函数体系也是如此被加载起来,和社区保持了高度的一致,也减少了很多的维护成本。

    小结

    从 midway serverless 的基础到入口的生成,生命周期以及应用迁移的方案介绍,这里涵盖的是 midway serverless 的架构防腐的一小部分,后续也将会有其他文章介绍不同的部分,感谢大家阅读,也欢迎大家关注 Midway。


    image.png
    关注「Alibaba F2E」
    把握阿里巴巴前端新动向

    ]]>
    《Java开发手册》解读:大整数传输为何禁用Long类型?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png
    8月3日,这个在我等码农心中具有一定纪念意义的日子里,《Java开发手册》发布了嵩山版。每次发布我都特别期待,因为总能找到一些程序员不得不重视的“血淋淋的巨坑”。比如这次,嵩山版中新增的模块——前后端规约,其中一条禁止服务端在超大整数下使用Long类型作为返回。

    image.png

    这个问题,我在实际开发中遇到过,所以印象也特别深。如果在业务初期没有评估到这一点,将订单ID这类关键信息,按照Long类型返回给前端,可能会在业务中后期高速发展阶段,突然暴雷,导致严重的业务故障。期望大家能够重视。

    这条规约给出了直接明确的避坑指导,但要充分理解背后的原理,知其所以然,还有很多点要思考。首先,我们来看几个问题,如果能说出所有问题的细节,就可直接跳过了,否则下文还是值得一看的:

    • 一问:JS的Number类型能安全表达的最大整型数值是多少?为什么(注意要求更严,是安全表达)?
    • 二问:在Long取值范围内,2的指数次整数转换为JS的Number类型,不会有精度丢失,但能放心使用么?
    • 三问:我们一般都知道十进制数转二进制浮点数有可能会出现精度丢失,但精度丢失具体怎么发生的?
    • 四问:如果不幸中招,服务端正在使用Long类型作为大整数的返回,有哪些办法解决?

    基础回顾

    在解答上面这些问题前,先介绍本文涉及到的重要基础:IEEE754浮点数标准。如果大家对IEEE754的细节烂熟于心的话,可以跳过本段内容,直接看下一段,问题解答部分。

    当前业界流行的浮点数标准是IEEE754,该标准规定了4种浮点数类型:单精度、双精度、延伸单精度、延伸双精度。前两种类型是最常用的。我们单介绍一下双精度,掌握双精度,自然就了解了单精度(而且上述问题场景也是涉及双精度)。

    双精度分配了8个字节,总共64位,从左至右划分是1位符号、11位指数、52位有效数字。如下图所示,以0.7为例,展示了双精度浮点数的存储方式。

    image.png

    存储位分配

    1)符号位:在最高二进制位上分配1位表示浮点数的符号,0表示正数,1表示负数。

    2)指数:也叫阶码位。

    在符号位右侧分配11位用来存储指数,IEEE754标准规定阶码位存储的是指数对应的移码,而不是指数的原码或补码。根据计算机组成原理中对移码的定义可知,移码是将一个真值在数轴上正向平移一个偏移量之后得到的,即[x]移=x+2^(n-1)(n为x的二进制位数,含符号位)。移码的几何意义是把真值映射到一个正数域,其特点是可以直观地反映两个真值的大小,即移码大的真值也大。基于这个特点,对计算机来说用移码比较两个真值的大小非常简单,只要高位对齐后逐个比较即可,不用考虑负号的问题,这也是阶码会采用移码表示的原因所在。

    由于阶码实际存储的是指数的移码,所以指数与阶码之间的换算关系就是指数与它的移码之间的换算关系。假设指数的真值为e,阶码为E ,则有 E = e + (2 ^ (n-1) - 1),其中 2 ^ (n-1) - 1 是IEEE754 标准规定的偏移量。则双精度下,偏移量为1023,11位二进制取值范围为[0,2047],因为全0是机器零、全1是无穷大都被当做特殊值处理,所以E的取值范围为[1,2046],减去偏移量,可得e的取值范围为[-1022,1023] 。

    3)有效数字:也叫尾数位。最右侧分配连续的52位用来存储有效数字,IEEE754标准规定尾数以原码表示。

    浮点数和十进制之间的转换

    在实际实现中,浮点数和十进制之间的转换规则有3种情况:

    1 规格化

    指数位不是全零,且不是全1时,有效数字最高位前默认增加1,不占用任何比特位。那么,转十进制计算公式为:

    (-1)^s*(1+m/2^52)*2^(E-1023)

    其中s为符号,m为尾数,E为阶码。比如上图中的0.7 :

    1)符号位:是0,代表正数。

    2)指数位:01111111110,转换为十进制,得阶码E为1022,则真值e=1022-1023=-1。

    3)有效数字:

    0110011001100110011001100110011001100110011001100110

    转换为十进制,尾数m为:1801439850948198。

    4)计算结果:

    (1+1801439850948198/2^52)*(2^-1) =0.6999999999999999555910790149937383830547332763671875

    经过显示优化算法后(在后文中详述),为0.7。

    2 非规格化

    指数位是全零时,有效数字最高位前默认为0。那么,转十进制计算公式:

    (-1)^s*(0+m/2^52)*2^(-1022)

    注意,指数位是-1022,而不是-1023,这是为了平滑有效数字最高位前没有1。比如非规格最小正值为:

    0x0.00000000000012^-1022=2^-52 2^-1022 = 4.9*10^-324

    3 特殊值

    指数全为1,有效数字全为0时,代表无穷大;有效数字不为0时,代表NaN(不是数字)。

    问题解答

    1 JS的Number类型能安全表达的最大整型数值是多少?为什么?

    规约中已经指出:

    在Long类型能表示的最大值是2的63次方-1,在取值范围之内,超过2的53次方(9007199254740992)的数值转化为JS的Number时,有些数值会有精度损失。

    “2的53次方”这个限制是怎么来的呢?如果看懂上文IEEE754基础回顾,不难得出:在浮点数规格化下,双精度浮点数的有效数字有52位,加上有效数字最高位前默认为1,共53位,所以JS的Number能保障无精度损失表达的最大整数是2的53次方。

    而这里的题问是:“能安全表达的最大整型”,安全表达的要求,除了能准确表达,还有正确比较。2^53=9007199254740992,实际上,

    9007199254740992+1 == 9007199254740992

    的比较结果为true。如下图所示:

    image.png

    这个测试结果足以说明2^53不是一个安全整数,因为它不能唯一确定一个自然整数,实际上9007199254740992、9007199254740993,都对应这个值。因此这个问题的答案是:2^53-1。

    2 在Long取值范围内,2的指数次整数转换为JS的Number类型,不会有精度丢失,但能放心使用么?

    规约中指出:

    在Long取值范围内,任何2的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数。

    后半句,我们就不说了,因为绝对没毛病,空间不限,不仅是任何整数可以精确表示,无理数我们也可以挑战一下。我们重点看前半句,根据本文前面所述基础回顾,双精度浮点数的指数取值范围为[-1022,1023],而指数是以2为底数。另外,双精度浮点数的取值范围,比Long大,所以,理论上Long型变量中2的指数次整数一定可以准确转换为JS的umber类型。但在JS中,实际情况,却是下面这样:

    image.png

    2的55次方的准确计算结果是:36028797018963968,而从上图可看到,JS的计算结果是:36028797018963970。而且直接输入36028797018963968,控制台显示结果是36028797018963970。

    这个测试结果,已经对本问题给出答案。为了确保程序准确,本文建议,在整数场景下,对于JS的Number类型使用,严格限制在2^53-1以内,最好还是信规约的,直接使用String类型。

    为什么会出现上面的测试现象呢?

    实际上,我们在程序中输入一个浮点数a,在输出得到a',会经历以下过程:

    1)输入时:按照IEEE754规则,将a存储。这个过程很有可能会发生精度损失。

    2)输出时:按照IEEE754规则,计算a对应的值。根据计算结果,寻找一个最短的十进制数a',且要保障a'不会和a隔壁浮点数的范围冲突。a隔壁浮点数是什么意思呢?由于存储位数是限定的,浮点数其实是一个离散的集合,两个紧邻的浮点数之间,还存在着无数的自然数字,无法表达。假设有f1、f2、f3三个升序浮点数,且它们之间的距离,不可能在拉近。则在这三个浮点数之间,按照范围来划分自然数。而浮点数输出的过程,就是在自己范围中找一个最适合的自然数,作为输出。如何找到最合适的自然数,这是一个比较复杂的浮点数输出算法,大家感兴趣的,可参考相关论文[1]。

    image.png

    所以,36028797018963968和36028797018963970这两个自然数,对应到计算机浮点数来说,其实是同一个存储结果,双精度浮点数无法区分它们,最终呈现哪一个十进制数,就看浮点数的输出算法了。下图这个例子可以说明这两个数字在浮点数中是相等的。另外,大家可以想想输入0.7,输出是0.7的问题,浮点数是无法精确存储0.7,输出却能够精确,也是因为有浮点数输出算法控制(特别注意,这个输出算法无法保证所有情况下,输入等于输出,它只是尽力确保输出符合正常的认知)。

    image.png

    扩展

    JS的Number类型既用来做整数计算、也用来做浮点数计算。其转换为String输出的规则也会影响我们使用,具体规则如下:
    image.png

    上面是一段典型的又臭又长但逻辑很严谨的描述,我总结了一个不是很严谨,但好理解的说法,大家可以参考一下:

    除了小数点前的数字位数(不算开始的0)少于22位,且绝对值大于等于1e-6的情况,其余都用科学计数法格式化输出。举例:

    image.png

    3 我们一般都知道十进制数转二进制浮点数有可能会出现精度丢失,精度丢失怎么发生的?

    通过前面IEEE754分析,我们知道十进制数存储到计算机,需要转换为二进制。有两种情况,会导致转换后精度损失:

    1)转换结果是无限循环数或无理数

    比如0.1转换成二进制为:

    0.0001 10011001100110011001100110011...

    其中0011在循环。将0.1转换为双精度浮点数二进制存储为:

    0 01111111011 1001100110011001100110011001100110011001100110011001

    按照本文前面所述基础回顾中的计算公式 (-1)^s(1+m/2^52)2^(E-1023)计算,可得转换回十进制为:0.09999999999999999。这里可以看出,浮点数有时是无法精确表达一个自然数,这个和十进制中1/3 =0.333333333333333...是一个道理。

    2)转换结果长度,超过有效数字位数,超过部分会被舍弃

    IEEE754默认是舍入到最近的值,如果“舍”和“入”一样接近,那么取结果为偶数的选择。

    另外,在浮点数计算过程中,也可能引起精度丢失。比如,浮点数加减运算执行步骤分为:

    零值检测 -> 对阶操作 -> 尾数求和 -> 结果规格化 -> 结果舍入

    其中对阶和规格化都有可能造成精度损失:

    • 对阶:是通过尾数右移(左移会导致高位被移出,误差更大,所以只能是右移),将小指数改成大指数,达到指数阶码对齐的效果,而右移出的位,会作为保护位暂存,在结果舍入中处理,这一步有可能导致精度丢失。
    • 规格化:是为了保障计算结果的尾数最高位是1,视情况有可能会出现右规,即将尾数右移,从而导致精度丢失。

    4 如果不幸中招,服务端正在使用Long类型作为大整数的返回,有哪些办法解决?

    需要分情况。

    1)通过Web的ajax异步接口,以Json串的形式返回给前端

    方案一:如果,返回Long型所在的POJO对象在其他地方无使用,那么可以将后端的Long型直接修改成String型。

    方案二:如果,返回给前端的Json串是将一个POJO对象Json序列化而来,并且这个POJO对象还在其他地方使用,而无法直接将其中的Long型属性直接改为String,那么可以采用以下方式:

    String orderDetailString = JSON.toJSONString(orderVO, SerializerFeature.BrowserCompatible);

    SerializerFeature.BrowserCompatible 可以自动将数值变成字符串返回,解决精度问题。

    方案三:如果,上述两种方式都不适合,那么这种方式就需要后端返回一个新的String类型,前端使用新的,并后续上线后下掉老的Long型(推荐使用该方式,因为可以明确使用String型,防止后续误用Long型)。

    2)使用node的方式,直接通过调用后端接口的方式获取

    方案一:使用npm的js-2-java的 java.Long(orderId) 方法兼容一下。

    方案二:后端接口返回一个新的String类型的订单ID,前端使用新的属性字段(推荐使用,防止后续踩坑)。

    引用

    [1]http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.52.2247&rank=2
    [2]《码出高效》

    ]]>
    语雀的技术架构演进之路-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    作者 | 不四

    每个技术人心中或多或少都有一个「产品梦」,好的技术需要搭配好的产品,才能让用户爱不释手,尤其是做一款知识服务型产品。

    作者何翊宇(花名:不四,微信:dead_horse)是蚂蚁金服体验技术部高级前端技术专家,语雀产品技术负责人。本文从技术架构的视角,回顾了语雀的原型、内部服务和对外商业化的全过程,并对函数计算在语雀架构演进过程中所扮演的角色做了详细的介绍。

    语雀是一个专业的云端知识库,用于团队的文档协作。现在已是阿里员工进行文档编写和知识沉淀的标配,并于 2018 年开始对外提供服务。

    原型阶段

    回到故事的开始。

    2016 年,语雀孵化自蚂蚁科技,当时,蚂蚁金融云需要一个工具来承载它的文档,负责的技术同学利用业余时间,搭建了这个文档工具。项目的初期,没有任何人员和资源支持,同时也是为了快速验证原型,技术选型上选择了最低成本的方案。

    底层服务完全基于体验技术部内部提供的 BaaS 服务和容器托管平台:

    • Object 服务:一个类 MongoDB 的数据存储服务;
    • File 服务:阿里云 OSS 的基础上封装的一个文件存储服务;
    • DockerLab:一个容器托管平台;

    image.png
    这些服务和平台都是基于 Node.js 实现的,专门给内部创新型应用使用,也正是由于有这些降低创新成本的内部服务,才给工程师们提供了更好的创新环境。

    语雀的应用层服务端,自然而然的选用了蚂蚁体验技术部开源的 Node.js Web 框架 Egg(蚂蚁内部的封装 Chair),通过一个单体 Web 应用实现服务端。应用层客户端也选用了 React 技术栈,结合内部的 antd,并采用 CodeMirror 实现了一个功能强大、体验优雅的 markdown 在线编辑器。

    当时仅仅是一个工程师的业余项目,采用内部专为创新应用提供的 BaaS 服务和一系列的开源技术,验证了在线文档工具这个产品原型。

    内部服务阶段

    2017年,随着语雀得到团队内部的认可,他的目标已经不仅仅是金融云的文档工具,而是成为阿里所有员工的知识管理平台。不仅面向技术人员 Markdown 编辑器,还向非技术知识创作者,提供了富文本编辑器,并选择了更“Web”的路线,在富文本编辑器中加入了公式、文本绘图、思维导图等特色功能。而随着语雀在知识管理领域的不断探索,知识管理的三层结构(团队、知识库、文档)开始成型。

    在此之上的协作、分享、搜索与消息动态等功能越来越复杂单纯的依靠 BaaS 服务已经无法满足语雀的业务需求了。

    为了应对业务发展带来的挑战,我们主要从下面几个点进行改造:

    • BaaS 服务虽然使用简单成本低,但是它们提供的功能不足以满足语雀业务的发展,同时稳定性上也有不足。所以我们将底层服务由 BaaS 替换成了阿里云的 IaaS 服务(MySQL、OSS、缓存、搜索等服务)。
    • Web 层仍然采用了 Node.js 与 Egg 框架,但是业务层借鉴 rails 社区的实践开始变成了一个大型单体应用,通过引入 ORM 构建数据模型层,让代码的分层更清晰。
    • 前端编辑器从 codeMirror 迁移到 Slate。为了更好的实现语雀编辑器的功能,我们内部 fork 了 Slate 进行深入开发,同时也自定义了一个独立的内容存储格式,以提供更高效的数据处理和更好的兼容性。

    image.png
    在内部服务阶段,语雀已经成为了一个正式的产品,通过在阿里内部的磨炼,语雀的产品形态基本定型。

    对外商业化阶段

    随着语雀在内部的影响力越来越大,一些离职出去创业的阿里校友们开始找到玉伯(蚂蚁体验技术部研究员):“语雀挺好用的,有没有考虑商业化之后让外面的公司也能够用起来?”

    经过小半年的酝酿和重构,2018 年初,语雀开始正式对外提供服务,进行商业化。

    当一个应用走出公司内到商业化环境中,面临的技术挑战一下子就变大了。最核心的知识创作管理部分的功能越来越复杂,表格、思维导图等新格式的加入,多人实时协同的需求对编辑器技术提出了更高的挑战。而为了更好的服务企业用户与个人用户,语雀在企业服务、会员服务等方面也投入了很大精力。在业务快速发展的同时,服务商业化对质量、安全和稳定性也提出了更高的要求。

    为了应对业务发展,语雀的架构也随之发生了演进:

    我们将底层的依赖完全上云,全部迁移到了阿里云上,阿里云不仅仅提供了基础的存储、计算能力,同时也提供了更丰富的高级服务,同时在稳定性上也有保障。

    • 丰富的云计算基础服务,保障语雀的服务端可以选用最适合语雀业务的的存储、队列、搜索引擎等基础服务;
    • 更多人工智能服务给语雀的产品带来了更多的可能性,包括 OCR 识图、智能翻译等服务,最终都直接转化成为了语雀的特色服务;

    而在应用层,语雀的服务端依然还是以一个基于 Egg 框架的大型的 Node.js Web 应用为主。但是随着功能越来越多,也开始将一些相对比较独立的服务从主服务中拆出去,可以把这些服务分成几类:

    • 微服务类:例如多人实时协同服务,由于它相对独立,且长连接服务不适合频繁发布,所以我们将其拆成了一个独立的微服务,保持其稳定性。
    • 任务服务类:像语雀提供的大量本地文件预览服务,会产生一些任务比较消耗资源、依赖复杂。我们将其从主服务中剥离,可以避免不可控的依赖和资源消耗对主服务造成影响。
    • 函数计算类:类似 Plantuml 预览、Mermaid 预览等任务,对响应时间的敏感度不高,且依赖可以打包到阿里云函数计算中,我们会将其放到函数计算中运行,既省钱又安全。

    随着编辑器越来越复杂,在 slate 的基础上进行开发遇到的问题越来越多。最终语雀还是走上了自研编辑器的道路,基于浏览器的 Contenteditable 实现了富文本编辑器,通过 Canvas 实现了表格编辑器,通过 SVG 实现了思维导图编辑器。
    image.png
    语雀的这个阶段(也是现在所处的阶段)是商业化阶段,但是我们仍然保持了一个很小的团队,通过 JavaScript 全栈进行研发。底层的服务全面上云,借力云服务打造语雀的特色功能。同时为企业级用户和个人知识工作者者提供知识创作和管理工具。

    和函数计算的不解之缘

    语雀是一个复杂的 Web 应用,也是一个典型的数据密集型应用(Data-Intensive Application),背后依赖了大量的数据库等云服务。语雀服务端是 Node.js 技术栈。当提到 node 的时候,可能立刻就会有几个词浮现在我们脑海之中:单线程(single-threaded)、非阻塞(non-blocking)、异步(asynchronously programming),这些特性一方面非常的适合于构建可扩展的网络应用,用来实现 Web 服务这类 I/O 密集型的应用,另一方面它也是大家一直对 node 诟病的地方,对 CPU 密集型的场景不够友好,一旦有任何阻塞进程的方法被执行,整个进程就被阻塞。

    像语雀这样用 node 实现整个服务端逻辑的应用,很难保证不会出现一些场景可能会消耗大量 CPU 甚至是死循环阻塞进程的,例如以 markdown 转换举例,由于用户的输入无法穷举,总有各种可能让转换代码进入到一个低效甚至是死循环的场景之中。在 node 刚出世的年代,很难给这些问题找到完美的解决办法,而即便是 Java 等基于线程并发模型的语言,在遇到这样的场景也很头痛,毕竟 CPU 对于 web 应用来说都是非常重要的资源。而随着基础设置越来越完善,当函数计算出现时,node 最大的短板看起来有了一个比较完美的解决方案。

    阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传,只需要为代码实际运行所消耗的资源付费,代码未运行则不产生费用。

    把函数计算引入之后,我们可以将那些 CPU 密集型、存在不稳定因素的操作统统放到函数计算服务中去执行,而我们的主服务再次回归到了 I/O 密集型应用模型,又可以愉快的享受 node 给我们带来的高效研发福利了!

    以语雀中遇到的一个实际场景来举例,用户传入了一些 HTML 或者 Markdown 格式的文档内容,我们需要将其转换成为语雀自己的文档格式。在绝大部分情况下,解析用户输入的内容都很快,然而依然存在某些无法预料到的场景会触发解析器的 bug 而导致死循环的出现,甚至我们不太敢升级 Markdown 解析库和相关插件以免引入更多的问题。但是随着函数计算的引入,我们将这个消耗 CPU 的转换逻辑放到函数计算上,语雀的主服务稳定性不会再被影响。
    image.png

    除了帮助 Web 系统分担一些 CPU 密集型操作以外,函数计算还能做什么呢?

    在语雀上我们支持各种代码形式来绘图,包括 Plantuml、公式、Mermaid,还有一些将文档导出成 PDF、图片等功能。这些场景有两个特点:

    • 他们依赖于一些复杂的应用软件,例如 Puppeteer、Graphviz 等;
    • 可能需要执行用户输入的内容;

    支持这类场景看似简单,通过 process.exec 子进程调用一下就搞定了。但是当我们想把它做成一个稳定的对外服务时,问题就出现了。这些复杂的应用软件可能从设计上并没有考虑要长期运行,长期运行时的内存占用、稳定性可能会有一些问题,同时在被大并发调用时,对 CPU 的压力非常大。再加上有些场景需要运行用户输入的代码,攻击者通过构建恶意输入,可以在服务器上运行攻击代码,非常危险。

    在没有引入函数计算之前,语雀为了支持这些功能,尽管单独分配了一个任务集群,在上面运行这些三方服务,接受主服务的请求来避免影响主服务的稳定性。但是为了解决上面提到的一系列问题还需要付出很大的成本:

    • 需要维持一个不小的任务集群,尽管可能大部分时间都用不上那么多资源。
    • 需要定时对三方应用软件进行重启,避免长时间运行带来的内存泄露,即便如此有些特殊请求也会造成第三方软件的不稳定。
    • 对用户的输入进行检测和过滤,防止黑客恶意攻击,而黑客的攻击代码很难完全防住,安全风险依旧很大。

    image.png
    最后语雀将所有的第三方服务都分别打包在函数中,将这个任务集群上的功能都拆分成了一系列的函数放到了函数计算上。通过函数计算的特点一下解决了上面的所有问题:

    • 函数计算的计费模式是按照代码实际运行的 CPU 时间计费,不需要长期维护一个任务集群了。
    • 函数计算上的函数运行时尽管会有一些常驻函数的优化,但是基本不用考虑长期运行带来的一系列问题,且每次调用之间都相互独立,不会互相影响。
    • 用户的输入代码是运行在一个沙箱容器中,即便不对用户输入做任何过滤,恶意攻击者也拿不到任何敏感信息,同时也无法进入内部网络执行代码,更加安全。

    image.png

    除了上面提到的这些功能之外,语雀最近还使用 OSS + 函数计算替换了之前使用的阿里云视频点播服务来进行视频和音频的转码。

    由于浏览器可以直接支持播放的音视频格式并不多,大量用户上传的视频想要能够直接在语雀上进行播放需要对它们进行转码,业界一般都是通过 FFmpeg 来对音视频进行转码的。转码服务也是一个典型的 CPU 密集型场景,如果要自己搭建视频转码集群会面临大量的资源浪费,而使用阿里云视频点播服务,成本也比较高,而且能够控制的东西也不够多。函数计算直接集成了 FFmpeg 提供音视频处理能力,并集成到应用中心,配合 SLS 完善了监控和数据分析。语雀将音视频处理从视频点播服务迁移到函数计算之后,通过优化压缩率、减少不必要的转码等优化,将费用降低至之前的 1/5。
    image.png
    从语雀的实践来看,语雀并没有像 SFF 一样将 Web 服务迁移到函数计算之上(SFF 模式并不是现在的函数计算架构所擅长的),但是函数计算在语雀整体的架构中对稳定性、安全性和成本控制起到了非常重要的作用。总结下来函数计算非常适合下面几种场景:

    • 对于时效性要求不算非常高的 CPU 密集型操作,分担主服务 CPU 压力。
    • 当做沙箱环境执行用户提交的代码。
    • 运行不稳定的三方应用软件服务。
    • 需要很强动态伸缩能力的服务。

    在引入函数计算之后,语雀现阶段的架构变成了以一个 Monolith Application 为核心,并将一些独立的功能模块根据使用场景和对能力的要求分别拆分成了 Microservices 和 Serverless 架构。应用架构与团队成员组成、业务形态息息相关,但是随着各种云服务与基础设施的完善,我们可以更自如的选择更合适的架构。
    image.png
    为什么要特别把 Serverless 单独拿出来说呢?还记得之前说 Node.js 是单线程,不适合 CPU 密集型任务么?

    由于 Serverless 的出现,我们可以将这些存在安全风险的,消耗大量 CPU 计算的任务都迁移到函数计算上。它运行在沙箱环境中,不用担心用户的恶意代码造成安全风险,同时将这些 CPU 密集型的任务从主服务中剥离,避免出现并发时阻塞主服务。按需付费的方式也可以大大节约成本,不需要为低频功能场景部署一个常驻服务。所以我们会尽量的把这类服务都迁移到 Serverless 上(如阿里云函数计算)。

    结语 | 语雀的技术栈选择

    语雀这几年一步步发展过来,背后的技术一直在演进,但是始终遵循了几条原则:

    • 技术栈选型要匹配产品发展阶段。产品在不同的阶段对技术提出的要求是不一样的,越前期,对迭代效率的要求越高,商业化规模化之后,对稳定性、性能的要求就会变高。不需要一上来就用最先进的技术方案,而是需要和产品阶段一起考虑和权衡。
    • 技术栈选型要结合团队成员的技术背景。语雀选择 JavaScript 全栈的原因是孵化语雀的团队,大部分都是 JavaScript 背景的程序员,同时 Node.js 在蚂蚁也算是一等公民,配套的设施相对完善。
    • 最重要的一点是,不论选择什么技术栈,安全、稳定、可维护(扩展)都是要考虑清楚的。用什么语言、用什么服务会变化,但是这些基础的安全意识、稳定性意识,如何编写可维护的代码,都是决定项目能否长期发展下去的重要因素。

    image.png
    关注「Alibaba F2E」
    把握阿里巴巴前端新动向

    ]]>
    电商搜索“无结果率高,体验差”怎么办?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 垂直电商与综合类电商相比,具有更精准的市场定位、更深化的产品与服务质量、更强的客户粘性和更独特的品牌附加度的优势,所以搜索性能的好坏直接影响着业务最终结果

    案例背景:

    某日活千万级的垂直类电商平台,业务以社区+商城形式开展,商城业务是商业收入的主要来源,大部分用户有明确的购买指向性,其中商品搜索天级PV3000万+,搜索引导的成交占比全部成交的60%以上,是站内最重要的功能,在用户满意度调研中发现对搜索体验吐槽连连,反馈的主要问题是站内商品搜不到,个人卖家发布的商品排序靠后

    搜索问题反馈

    (1)用户:搜索不到想要的商品,体验差;
    (2)运营: 站内搜索的无结果率接近60%,说明每天有1800万的PV转换为0,流量白白浪费;
    1597059169694-cf863434-8e08-45b0-8689-f4c4373d2909.png

    (3)个人卖家: 个人卖家发布的商品排序靠后;打击发布商品积极性,影响平台价值定位和圈层生态,从而直接影响平台收益;

    搜索问题成因:

    (1)垂直小众的圈子,对于商品的叫法非常多样,并形成主流,用户搜索中不一定按照实际商品名称进行查询(例如:用户会搜“喷泡”其实想找的商品是Air Jordan AirFoamposite系列的鞋);
    (2)用户搜索表述错误(例如:搜“连衣群”其实是想搜“连衣裙”);
    (3)站内的搜索结果分3个tab呈现,分别为“销量”、“价格”、“新品”,用户搜索后默认展现的是“销量”tab下的结果,因此个人卖家发布产品由于销量少或无销量自然导致排序靠后,曝光量小,销量难增长,恶性循环;

    问题分析:

    (1)针对召回结果不理想情况,经分析发现自建ES服务没有对搜索关键词做智能的语义理解,甚至有些实体名词分词还是错误的;
    (2)针对排序问题,经分析需增加“综合”搜索结果呈现,根据核心索引优化排序算法;

    开放搜索解决方案:

    1595474121408-2e973285-9869-47c1-b46e-3c85c4cf4526.png
    (1)核心索引上配置使用了电商行业的查询语义理解,包括同义词、停用词、电商拼写纠错、电商实体识别等 ,就是这些功能将搜索关键词进行了系统可识别的智能改写,扩大召回相关结果;
    (2)针对商品别称问题,运营同学通过平时运营积累的专业词汇可视化同步到开放搜索做查询语义理解功能的补丁,通过灵活干预得以解决;
    (3)创建核心索引“商品标题、颜色、类目名称、品牌名称、运营优化文案、系列名称等”,将它们引入到排序表达式中,通过多个维度构建出更精细化的排序模型;
    (4)增加“综合”搜索tab,并默认展示“综合”搜索结果

    实践后的搜索性能对比:

    (1)搜索“詹姆斯球衣”输入成“詹慕斯球衣”
    • Before: 服务无法召回相关结果;
    • After: 纠错改写为“詹姆斯”进行查询,并且前端会提示“以下的结果是查询:詹姆斯球衣,仍然搜索詹慕斯球衣”;IMG_5180.PNG
    (2)搜索“喷泡”
    • Before:无法召回相关结果;
    • After: 召回到Air Jordan AirFoamposite系列的鞋
    1597058789960-e2f3639e-fc98-4d87-a9f1-4da9c9322289.jpeg
    (3)排序效果
    • Before:以销量默认排名,个人卖家排序靠后
    • After:提高搜索相关性增加更多商品曝光机会

    ]]>
    DevUP 沙龙 | 八月北京、青岛、厦门燥起来-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 炎热的周末不知去哪里耍,不如来参加阿里云开发者 DevUP 沙龙吧。三大城市火热来袭,专家面对面交流,更有实操环节加深理解。放松学习的同时还有机会结交志同道合的小伙伴,约约约!

    报名直通车:

    8月22日【青岛】活动:阿里云开发者 DevUP 沙龙 -青岛站 -阿里巴巴微服务技术的应用与实践

    8月22日【厦门】活动:阿里云人工智能+大数据的实践与应用-阿里云开发者DevUP 沙龙·厦门站

    8月27日【北京】活动:阿里云开发者 DevUP 沙龙 -北京站 -阿里云企业AIOT技术与解决方案沙龙

    大咖分享还有动手操作,更多精彩尽在【阿里云开发者 DevUP 沙龙】。活动时间均在周末,放松学习同时还有机会结交志同道合的小伙伴,还等什么快来参加吧<<<<<<<

    活动一:阿里云开发者 DevUP 沙龙 -青岛站 -阿里巴巴微服务技术的应用与实践

    微服务开发中,SpringCloud作为Spring生态中的针对微服务的技术框架,越来越受到各个企业技术人员的追捧。但是,SpringCloud中一些组件,在实践使用中,存在一定的局限。SpringCloudAlibaba,横空出世,替换了SpringCloud中的一些组件,使微服务在实践中,能够更便捷的、更优雅的实现落地。

    8月15日,上海ACE同城会特邀阿里平头哥的2位语音产品专家,从操作系统融合、解决方案支撑、语音市场趋势规划等方面,跟大家一起畅聊语音圈!

    时间:8月22日(周四)13:30-16:10
    地点:山东省青岛市崂山区海尔路170号鑫裕和大厦8楼

    活动亮点:
    1、面基!在青岛举办的线下技术沙龙。
    2、阵容!各类技术专家在线实践教学。
    3、干货!快速了解微服务相关知识。

    报名地址:https://survey.aliyun.com/apps/zhiliao/7irPO_3gv

    image.png

    活动二:阿里云人工智能+大数据的实践与应用-阿里云开发者DevUP 沙龙·厦门站

    阿里云开发者社区携手云原生后端、阿里云基础产品事业部共同出品了本次沙龙。

    现场邀请了4位阿里云专家为大家分享云计算行业人工智能+大数据的实践与应用,期待您的参与。

    时间:8月22日(周六)13:30——17:20
    地点:厦门市集美区杏林湾路营运中心1号楼2楼

    报名地址:https://developer.aliyun.com/article/769954

    image.png

    活动三:阿里云开发者 DevUP 沙龙 -北京站 -阿里云企业AIOT技术与解决方案沙龙

    在阿里云线上市场,近2万解决方案已经应用在几十万个城市,企业和工厂,无数家庭受天猫精灵带来的只能生活,物联网已经是基础设施,更是企业的核心竞争力。

    时间:8月27日(周四)13:30-17:30
    地点:北京市朝阳区望京东园4区4号楼——阿里中心·望京B座 2F-13文韵阁

    活动亮点:
    1、面基!在阿里园区举办的线下技术沙龙。
    2、阵容!阿里云各类技术专家在线教学。
    3、干货!快速打造企业级物联网平台和应用,实现数字化转型。

    报名地址:https://developer.aliyun.com/article/769911

    image.png

    各位小伙伴是不是按耐不住内心的激动了呢?快快点击下方的阅读原文报名,来和阿里巴巴的技术专家们深度交流吧!

    ACE同城会

    阿里云 ACE全称 Alibaba Cloud Engineer。意为阿里云的工程师、代表着云计算的建设者。同时“ACE”又是扑克牌中的“A”,因此阿里云ACE也寓意着是云计算领域王牌的一群人。

    ACE同城会是遍布在全国的开发者社群,作为国内优秀的开发者圈子,为所有开发者提供学习、交流的机会和平台。

    在全国48个城市成立了同城会,覆盖13万开发者,举办超100场线下活动。

    官网:https://mvp.aliyun.com/ace

    现开发者社区招募会长和班委,有意者钉钉扫码联系运营同学,加好友时请备注【ACE同城会会长/班委】。

    test

    ]]>
    倒计时方案深入分析-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 倒计时方案深入分析

    目录介绍

    • 01.使用多种方式实现倒计时
    • 02.各种倒计时器分析
    • 03.CountDownTimer解读
    • 04.Timer和TimerTask解读
    • 05.自定义倒计时器案例

    01.使用多种方式实现倒计时

    • 首先看一下需求

      • 要求可以创建多个倒计时器,可以暂停,以及恢复暂停。可以自由设置倒计时器总时间,倒计时间隔。下面会一步步实现一个多功能倒计时器。
    • 01.使用Handler实现倒计时

      • mHandler + runnable ,这种是最常见的一种方式。实质是不断调用mHandler.postDelayed(this, 1000)达到定时周期目的
    • 02.使用CountDownTimer实现倒计时

      • 也是利用mHandler + runnable,在此基础上简单封装一下。使用场景更强大,比如一个页面有多个倒计时器,用这个就很方便……
    • 03.利用Timer实现定时器

      • 使用Timer + TimerTask + handler方式实现倒计时
    • 04.使用chronometer控件倒计时

      • 新出的继承TextView组件,里头是使用了View.postDelayed + runnable实现倒计时
    • 05.利用动画实现倒计时

      • 这种方式用的比较少,但也是一种思路。主要是设置动画时间,在onAnimationUpdate监听设置倒计时处理
    • 具体代码案例可以看

    • 具体代码案例

    02.各种倒计时器分析

    • 第一种利用Handler实现倒计时

      • 这种用的很普遍,但存在一个问题。如果是一个页面需要开启多个倒计时【比如列表页面】,则比较难处理。
    • 第二种使用CountDownTimer实现倒计时

      • new CountDownTimer(5000, 1000).start()

        • 期待的效果是:“5-4-3-2-1-finish”或者“5-4-3-2-1-0”。这里,显示 0 和 finish 的时间应该是一致的,所以把 0 放在 onFinish() 里显示也可以。但实际有误差……
      • 存在的几个问题

        • 问题1. 每次 onTick() 都会有几毫秒的误差,并不是期待的准确的 "5000, 4000, 3000, 2000, 1000, 0"。
        • 问题2. 多运行几次,就会发现这几毫秒的误差,导致了计算得出的剩余秒数并不准确,如果你的倒计时需要显示剩余秒数,就会发生 秒数跳跃/缺失 的情况(比如一开始从“4”开始显示——缺少“5”,或者直接从“5”跳到了“3”——缺少“4”)。
        • 问题3. 最后一次 onTick() 到 onFinish() 的间隔通常超过了 1 秒,差不多是 2 秒左右。如果你的倒计时在显示秒数,就能很明显的感觉到最后 1 秒停顿的时间很长。
        • 问题4. 如果onTick耗时超时,比如超过了1000毫秒,则会导致出现onTick出现跳动问题
      • 解决方案

        • 具体看lib中的CountDownTimer类。下面也会分析到
        • 注意:onTick方法中如何执行耗时操作【大于1秒的执行代码】,建议使用handler消息机制进行处理,避免出现其他问题。
    • 第三种利用Timer实现定时器

      • 注意点

        • Timer和TimerTask都有cancel方法,而且最好同时调用;如果已经cancel,下次必须创建新的Timer才能schedule。
      • 可能存在的问题

        • 如果你在当前的activity中schedule了一个task,但是没有等到task结束,就按Back键finish了当前的activity,Timer和TimerTask并不会自动cancel或者销毁,它还会在后台运行,此时如果你在task的某个阶段要调起一个控件(比如AlertDialog),而该控制依赖被销毁的activity,那么将会引发crash。
        • 所以建议在页面销毁的时候,将Timer和TimerTask都有cancel结束并且设置成null
        • Timer 的方式实现定时任务,用来做倒计时是没有问题的。但是如果用来执行周期任务,恰好又有多个任务,恰好两个任务之间的时间间隔又比前一个任务执行时间短就会发生定时不准确的现象了。Timer 在执行过程中如果任务跑出了异常,Timer 会停止所有的任务。Timer 执行周期任务时依赖系统时间,系统时间的变化会引起 Timer 任务执行的变化。

    03.CountDownTimer解读

    03.1 来看一个问题

    • 先看案例代码,如下所示

      • 期待的效果是:“5-4-3-2-1-finish”或者“5-4-3-2-1-0”。这里,显示 0 和 finish 的时间应该是一致的,所以把 0 放在 onFinish() 里显示也可以。
      mCountDownTimer = new CountDownTimer(5000, 1000) {
          @Override
          public void onTick(long millisUntilFinished) {
              Log.i(TAG, "----倒计时----onTick--"+millisUntilFinished);
          }
      
          public void onFinish() {
              Log.i(TAG, "----倒计时----onFinish");
          }
      };
    • 然后看一下打印日志,如下所示

      2020-08-05 10:04:28.742 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--5000
      2020-08-05 10:04:29.744 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--3998
      2020-08-05 10:04:30.746 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--2997
      2020-08-05 10:04:31.746 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--1996
      2020-08-05 10:04:32.747 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--995
      2020-08-05 10:04:33.747 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onFinish
      2020-08-05 10:04:45.397 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--4999
      2020-08-05 10:04:46.398 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--3998
      2020-08-05 10:04:47.400 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--2996
      2020-08-05 10:04:48.402 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--1994
      2020-08-05 10:04:49.405 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onTick--992
      2020-08-05 10:04:50.401 17266-17266/com.yc.yctimer I/CountDownTimer: ----倒计时----onFinish
    • 可以看到有几个问题:

      • 问题1. 每次 onTick() 都会有几毫秒的误差,并不是期待的准确的 "5000, 4000, 3000, 2000, 1000, 0"。
      • 问题2. 多运行几次,就会发现这几毫秒的误差,导致了计算得出的剩余秒数并不准确,如果你的倒计时需要显示剩余秒数,就会发生 秒数跳跃/缺失 的情况(比如一开始从“4”开始显示——缺少“5”,或者直接从“5”跳到了“3”——缺少“4”)。
      • 问题3. 最后一次 onTick() 到 onFinish() 的间隔通常超过了 1 秒,差不多是 2 秒左右。如果你的倒计时在显示秒数,就能很明显的感觉到最后 1 秒停顿的时间很长。

    03.3 分析时间误差

    • 为什么会存在这个问题

      • 先看start()方法,计算的 mStopTimeInFuture(未来停止倒计时的时刻,即倒计时结束时间) 加了一个 SystemClock.elapsedRealtime() ,系统自开机以来(包括睡眠时间)的毫秒数,也可以叫“系统时间戳”。
      • 即倒计时结束时间为“当前系统时间戳 + 你设置的倒计时时长 mMillisInFuture ”,也就是计算出的相对于手机系统开机以来的一个时间。在下面代码中打印日志看看
      public synchronized final void start() {
          if (mMillisInFuture <= 0 && mCountdownInterval <= 0) {
              throw new RuntimeException("you must set the millisInFuture > 0 or countdownInterval >0");
          }
          mCancelled = false;
          long elapsedRealtime = SystemClock.elapsedRealtime();
          mStopTimeInFuture = elapsedRealtime + mMillisInFuture;
          CountTimeTools.i("start → mMillisInFuture = " + mMillisInFuture + ", seconds = " + mMillisInFuture / 1000 );
          CountTimeTools.i("start → elapsedRealtime = " + elapsedRealtime + ", → mStopTimeInFuture = " + mStopTimeInFuture);
          mPause = false;
          mHandler.sendMessage(mHandler.obtainMessage(MSG));
          if (mCountDownListener!=null){
              mCountDownListener.onStart();
          }
      }
      
      @SuppressLint("HandlerLeak")
      private Handler mHandler = new Handler() {
          @Override
          public void handleMessage(@NonNull Message msg) {
              synchronized (CountDownTimer.this) {
                  if (mCancelled) {
                      return;
                  }
                  //剩余毫秒数
                  final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                  if (millisLeft <= 0) {
                      mCurrentMillisLeft = 0;
                      if (mCountDownListener != null) {
                          mCountDownListener.onFinish();
                          CountTimeTools.i("onFinish → millisLeft = " + millisLeft);
                      }
                  } else if (millisLeft < mCountdownInterval) {
                      mCurrentMillisLeft = 0;
                      CountTimeTools.i("handleMessage → millisLeft < mCountdownInterval !");
                      // 剩余时间小于一次时间间隔的时候,不再通知,只是延迟一下
                      sendMessageDelayed(obtainMessage(MSG), millisLeft);
                  } else {
                      //有多余的时间
                      long lastTickStart = SystemClock.elapsedRealtime();
                      CountTimeTools.i("before onTick → lastTickStart = " + lastTickStart);
                      CountTimeTools.i("before onTick → millisLeft = " + millisLeft + ", seconds = " + millisLeft / 1000 );
                      if (mCountDownListener != null) {
                          mCountDownListener.onTick(millisLeft);
                          CountTimeTools.i("after onTick → elapsedRealtime = " + SystemClock.elapsedRealtime());
                      }
                      mCurrentMillisLeft = millisLeft;
                      // 考虑用户的onTick需要花费时间,处理用户onTick执行的时间
                      long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
                      CountTimeTools.i("after onTick → delay1 = " + delay);
                      // 特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
                      // 注意,在onTick回调的方法中,不要做些耗时的操作
                      boolean isWhile = false;
                      while (delay < 0){
                          delay += mCountdownInterval;
                          isWhile = true;
                      }
                      if (isWhile){
                          CountTimeTools.i("after onTick执行超时 → delay2 = " + delay);
                      }
                      sendMessageDelayed(obtainMessage(MSG), delay);
                  }
              }
          }
      };
    • 然后看一下日志

      2020-08-05 13:36:02.475 8742-8742/com.yc.yctimer I/CountDownTimer: start → mMillisInFuture = 5000, seconds = 5
      2020-08-05 13:36:02.475 8742-8742/com.yc.yctimer I/CountDownTimer: start → elapsedRealtime = 122669630, → mStopTimeInFuture = 122674630
      2020-08-05 13:36:02.478 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 122669634
      2020-08-05 13:36:02.478 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 4996, seconds = 4
      2020-08-05 13:36:02.479 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 122669635
      2020-08-05 13:36:02.479 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = 999
      2020-08-05 13:36:03.480 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 122670636
      2020-08-05 13:36:03.480 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 3994, seconds = 3
      2020-08-05 13:36:03.483 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 122670639
      2020-08-05 13:36:03.484 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = 996
      2020-08-05 13:36:04.482 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 122671638
      2020-08-05 13:36:04.483 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 2992, seconds = 2
      2020-08-05 13:36:04.486 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 122671642
      2020-08-05 13:36:04.486 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = 996
      2020-08-05 13:36:05.485 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 122672641
      2020-08-05 13:36:05.485 8742-8742/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 1989, seconds = 1
      2020-08-05 13:36:05.488 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 122672644
      2020-08-05 13:36:05.488 8742-8742/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = 997
      2020-08-05 13:36:06.487 8742-8742/com.yc.yctimer I/CountDownTimer: handleMessage → millisLeft < mCountdownInterval !
      2020-08-05 13:36:07.481 8742-8742/com.yc.yctimer I/CountDownTimer: onFinish → millisLeft = -3
    • 分析一下日志

      • 倒计时 5 秒,而 onTick() 一共只执行了 4 次。分别是出现4,3,2,1
      • start() 启动计时时,mMillisInFuture = 5000。且根据当前系统时间戳(记为 elapsedRealtime0 = 122669630,开始 start() 倒计时时的系统时间戳)计算了倒计时结束时相对于系统开机时的时间点 mStopTimeInFuture。
      • 此后到第一次进入 handleMessage() 时,中间经历了很短的时间 122669630 - 122669634 = 6 毫秒。
      • handleMessage() 这里精确计算了程序执行时间,虽然是第一次进入 handleMessage,也没有直接使用 mStopTimeInFuture,而是根据程序执行到此处时的 elapsedRealtime() (记为 elapsedRealtime1)来计算此时剩余的倒计时时长。
      • millisLeft = 4996,进入 else,执行 onTick()方法回调。所以第一次 onTick() 时,millisLeft = 4996,导致计算的剩余秒数是“4996/1000 = 4”,所以倒计时显示秒数是从“4”开始,而不是“5”开始。这便是前面提到的 问题1 和 问题2。
      • 考虑用户的onTick需要花费时间,处理用户onTick执行的时间,于是便发出一个延迟delay时间的消息sendMessageDelayed(obtainMessage(MSG), delay);在日志里看到delay1 = 997

    03.3 onTick耗时超时

    • 上面分析到了用户的onTick需要花费时间,如果delay < 0则需要特殊处理,这个究竟是什么意思呢?下面来分析一下
    • 分析一下下面这个while循环作用

      // 考虑用户的onTick需要花费时间,处理用户onTick执行的时间
      long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
      CountTimeTools.i("after onTick → delay1 = " + delay);
      // 特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
      while (delay < 0){
          delay += mCountdownInterval;
      }
      CountTimeTools.i("after onTick → delay2 = " + delay);
      sendMessageDelayed(obtainMessage(MSG), delay);
      • 如果这次 onTick() 执行时间太长,超过了 mCountdownInterval ,那么执行完 onTick() 后计算得到的 delay 是一个负数,此时直接跳到下一次 mCountdownInterval 间隔,让 delay + mCountdownInterval。
    • 举一个例子来说一下,不然这里不太好理解

      • 假如设定每 1000 毫秒执行一次 onTick()。假设第一次 onTick() 开始前时的相对于手机系统开机时间的剩余倒计时时长是 5000 毫秒, 执行完这次 onTick() 操作消耗了 1015 毫秒,超出了我们设定的 1000 毫秒的间隔,那么第一次计算的 delay = 1000 - 1015 = -15 < 0,那么负数意味着什么呢?
      • 本来我们设定的 onTick() 调用间隔是 1000 毫秒,可是它执行完一次却用了 1015 毫秒,现在剩余倒计时还剩下 5000 - 1015 = 3985 毫秒,本来第二次 onTick() 按期望应该是在 4000 毫秒时开始执行的,可是此时第一次的 onTick() 却还未执行完。所以第二次 onTick() 就会被延迟 delay = -15 + 1000 = 985 毫秒,也就是到剩余 3000 毫秒时再执行了。
      • 那么此时就会 3985 / 1000 = 3,就会从5过度到3;依次类推,后续的delay延迟985毫秒后执行sendMessageDelayed,会导致时间出现跳跃性变动。具体可以看一下下面的例子……
    • onTick()做耗时操作会出现什么情况

      • 比如下面,看打印日志可知:4,2没有,这就意味着这个阶段没有执行到onTick()方法,而如果你在这个里有业务逻辑与时间节点有关,则可能会出现bug
      2020-08-05 13:58:00.657 11912-11912/com.yc.yctimer I/CountDownTimer: start → mMillisInFuture = 5000, seconds = 5
      2020-08-05 13:58:00.657 11912-11912/com.yc.yctimer I/CountDownTimer: start → elapsedRealtime = 123987813, → mStopTimeInFuture = 123992813
      2020-08-05 13:58:01.781 11912-11912/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 123988937
      2020-08-05 13:58:01.781 11912-11912/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 3876, seconds = 3
      2020-08-05 13:58:02.858 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 123990014
      2020-08-05 13:58:02.858 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = -77
      2020-08-05 13:58:02.858 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick执行超时 → delay2 = 923
      2020-08-05 13:58:03.784 11912-11912/com.yc.yctimer I/CountDownTimer: before onTick → lastTickStart = 123990940
      2020-08-05 13:58:03.784 11912-11912/com.yc.yctimer I/CountDownTimer: before onTick → millisLeft = 1873, seconds = 1
      2020-08-05 13:58:04.896 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick → elapsedRealtime = 123992052
      2020-08-05 13:58:04.896 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick → delay1 = -112
      2020-08-05 13:58:04.896 11912-11912/com.yc.yctimer I/CountDownTimer: after onTick执行超时 → delay2 = 888
      2020-08-05 13:58:05.788 11912-11912/com.yc.yctimer I/CountDownTimer: onFinish → millisLeft = -130
    • onTick方法中如何执行耗时操作【大于1秒的执行代码】

      • 建议使用handler消息机制进行处理,避免出现其他问题。

    03.4 代码改进完善

    • 针对 问题1 和 问题 2:

      • 问题描述

        • 问题1. 每次 onTick() 都会有几毫秒的误差,并不是期待的准确的 "5000, 4000, 3000, 2000, 1000, 0"。
        • 问题2. 多运行几次,就会发现这几毫秒的误差,导致了计算得出的剩余秒数并不准确,如果你的倒计时需要显示剩余秒数,就会发生 秒数跳跃/缺失 的情况(比如一开始从“4”开始显示——缺少“5”,或者直接从“5”跳到了“3”——缺少“4”)。
      • 解决方案

        • 这2个问题可以放在一起处理,网上也有很多人对这里做了改进,那就是给我们的 倒计时时长扩大一点点,通常是手动将 mMillisInFuture 扩大几十毫秒
      • 效果

        • 这里多加了 20 毫秒,运行一下(举个栗子)。倒计时打印日志:“5,4,3,2,1,finish”,

    04.Timer和TimerTask解读

    04.1 Timer和TimerTask方法

    • Timer核心方法如下所示

      //安排指定任务在指定时间执行。如果时间在过去,任务被安排立即执行。
      void schedule(TimerTask task, long delay)
      //将指定的任务调度为重复执行<i>固定延迟执行</i>,从指定的延迟开始。后续执行大约按按指定周期间隔的规则间隔进行。
      void schedule(TimerTask task, long delay, long period)
      • 第一个方法只执行一次;
      • 第二个方式每隔period执行一次,delay表示每次执行的延时时间,其实主要表现在第一次的延时效果,比如delay设置为0,那么立马执行task内容,如果设置为1000,那么第一次执行task会有一秒的延时效果。
    • TimerTask核心方法

      • TimerTask用于继承(或者直接定义并初始化匿名类),并重写run方法,定义自己的业务逻辑。
      //取消此计时器任务。如果任务被计划为一次性执行而尚未运行,或尚未被计划,则它将永远不会运行。
      //如果任务被安排为重复执行,它将永远不会再运行。(如果在此调用发生时任务正在运行,则任务将运行到完成,但将不再运行。)
      public boolean cancel() {
          synchronized(lock) {
              boolean result = (state == SCHEDULED);
              state = CANCELLED;
              return result;
          }
      }
    • 关于结束定时器

      • Timer和TimerTask都有cancel方法,而且最好同时调用;如果已经cancel,下次必须创建新的Timer才能schedule。
      public void destroyTimer() {
          if (mTimer != null) {
              mTimer.cancel();
              mTimer = null;
          }
          if (mTimerTask != null) {
              mTimerTask.cancel();
              mTimerTask = null;
          }
      }
    • 可能存在的问题

      • 如果你在当前的activity中schedule了一个task,但是没有等到task结束,就按Back键finish了当前的activity,Timer和TimerTask并不会自动cancel或者销毁,它还会在后台运行,此时如果你在task的某个阶段要调起一个控件(比如AlertDialog),而该控制依赖被销毁的activity,那么将会引发crash。
      • 所以建议在页面销毁的时候,将Timer和TimerTask都有cancel结束并且设置成null
      • Timer 的方式实现定时任务,用来做倒计时是没有问题的。但是如果用来执行周期任务,恰好又有多个任务,恰好两个任务之间的时间间隔又比前一个任务执行时间短就会发生定时不准确的现象了。Timer 在执行过程中如果任务跑出了异常,Timer 会停止所有的任务。Timer 执行周期任务时依赖系统时间,系统时间的变化会引起 Timer 任务执行的变化。

    04.2 Timer原理分析

    • 其基本处理模型是单线程调度的任务队列模型,Timer不停地接受调度任务,所有任务接受Timer调度后加入TaskQueue,TimerThread不停地去TaskQueue中取任务来执行。

    • 此种方式的不足之处为当某个任务执行时间较长,以致于超过了TaskQueue中下一个任务开始执行的时间,会影响整个任务执行的实时性。为了提高实时性,可以采用多个消费者一起消费来提高处理效率,避免此类问题的实现。

    04.3 TimerTask分析

    • 源代码如下所示

      • 可以发现TimerTask是实现Runnable接口的一个抽象类。如果直接继承该类并且实现该类的run() 方法就可以了,里面包含这种对应的状态。
      public abstract class TimerTask implements Runnable {
          final Object lock = new Object();
          int state = VIRGIN;
          //表示尚未计划此任务(也表示初始状态)
          static final int VIRGIN = 0;
          //表示正在执行任务状态
          static final int SCHEDULED   = 1;
          //表示执行完成状态
          static final int EXECUTED    = 2;
          //取消状态
          static final int CANCELLED   = 3;
          //下次执行任务的时间
          long nextExecutionTime;
          //执行时间间隔
          long period = 0;
          //子类需要实现该方法,执行的任务的代码在该方法中实现
          public abstract void run();
          //取消任务,从这里我们可以很清楚知道取消任务就是修改状态
          public boolean cancel() {
              synchronized(lock) {
                  boolean result = (state == SCHEDULED);
                  state = CANCELLED;
                  return result;
              }
          }
      }

    04.4 Timer源码分析

    • Timer才是真正的核心,在创建Timer对象的同时也创建一个TimerThread对象,该类集成Thread,本质上就是开启了一个线程。

      public class Timer {
          //创建一个任务队列
          private final TaskQueue queue = new TaskQueue();
          //创建一个Thread线程对象,并且将queue队列传进去
          private final TimerThread thread = new TimerThread(queue);
          public Timer() {
              this("Timer-" + serialNumber());
          }
      
          public Timer(boolean isDaemon) {
              this("Timer-" + serialNumber(), isDaemon);
          }
      
          public Timer(String name) {
              thread.setName(name);
              thread.start();
          }
      
          public Timer(String name, boolean isDaemon) {
              thread.setName(name);
              thread.setDaemon(isDaemon);
              thread.start();
          }
      }
    • 然后看一下TimerThread线程的源码,如下所示

      • 首先看run方法中的mainLoop(),开启一个不断循环的线程如果队列中不存在任务则阻塞当前的线程,直到队列中添加任务以后唤醒线程。
      • 然后获取队列中执行时间最小的任务,如果该任务的状态是取消的话则从队列中移除掉再从队列中重新获取。
      • 最后判断当前的时间是否大于等于任务的执行的时间,如果任务的执行时间还未到则当前线程再阻塞一段时间,同时我们还要将该任务重新扔到任务队列中重新排序,我们必须保证队列中的第一个任务的执行时间是最小的。
      • 执行完mainLoop()方法完后,接着就将newTasksMayBeScheduled设置为false,并且清空队列中所有的任务。
      • 思考一下,这里的最小任务是什么意思?先把这个疑问记着……
      class TimerThread extends Thread {
          boolean newTasksMayBeScheduled = true;
          private TaskQueue queue;
      
          TimerThread(TaskQueue queue) {
              this.queue = queue;
          }
      
          public void run() {
              try {
                  mainLoop();
              } finally {
                  synchronized(queue) {
                  //同时将状态置为false
                  newTasksMayBeScheduled = false;
                  //清空队列中所有的任务
                  queue.clear();
              }
          }
      
          private void mainLoop() {
              //while死循环
              while (true) {
                  try {
                      TimerTask task;
                      boolean taskFired;
                      synchronized(queue) {
                          //如果任务队列为空并且该标志位 true的话,则该线程一直进行等待中,直到队列中有任务进来的时候执行 queue.notify才会解除阻塞
                          while (queue.isEmpty() && newTasksMayBeScheduled)
                              queue.wait();
                          //如果队列中的内容为空的话直接跳出循环,外部调用者可能取消了Timer
                          if (queue.isEmpty())
                              break;
                          long currentTime, executionTime;
                          //获取队列中最近执行时间最小的任务(也就是最近需要执行的任务)
                          task = queue.getMin();
                          synchronized(task.lock) {
                              //如果该任务的状态是取消状态的话,那从队列中移除这个任务,然后继续执行循环队列操作
                              if (task.state == TimerTask.CANCELLED) {
                                  queue.removeMin();
                                  continue;
                              }
                              //获取当前系统时间
                              currentTime = System.currentTimeMillis();
                              //获取下一个目标要执行的时间
                              executionTime = task.nextExecutionTime;
                              //如果下一个目标要执行的时间大于等于等于时间了,表示要执行任务了
                              if (taskFired = (executionTime<=currentTime)) {
                                  //如果task的时间间隔为0,表示只执行一次该任务
                                  if (task.period == 0) {
                                      //将任务状态改为已执行状态,同时从队列中删除该任务
                                      queue.removeMin();
                                      task.state = TimerTask.EXECUTED;
                                  } else {
                                      //将任务重新跟队列中的任务进行排列,要始终保证第一个task的时间是最小的
                                      queue.rescheduleMin(task.period<0 ? currentTime   - task.period
                                                      : executionTime + task.period);
                                  }
                              }
                          }
                          //这里表示最近要执行的任务时间没有到,那么再让当前的线程阻塞一段时间
                          if (!taskFired)
                              queue.wait(executionTime - currentTime);
                      }
                      //表示要执行的任务时间已经到了,那么直接调用任务的run() 执行代码
                      if (taskFired)
                          task.run();
                  } catch(InterruptedException e) {
                  }
              }
          }
      }
    • 接着再来看一下TaskQueue队列的源代码

      • 可以发现这个队列使用数组实现的,如果超过了128的话则扩容为原来的两倍。这个代码不多,注释写的很详细了,没什么好讲的……
      public class TaskQueue {
          //创建一个数组为128的数组存放需要执行的任务,如果超过了128的话则扩容为原来的两倍
          private TimerTask[] queue = new TimerTask[128];
          //用于统计队列中任务的个数
          private int size = 0;
          //返回队列中任务的个数
          int size() {
              return size;
          }
      
          //依次遍历数组中的任务,并且置为null,有利于内存回收,注意这里的下标是从1开始计算的,不是从0
          void clear() {
              for (int i=1; i<=size; i++)
                  queue[i] = null;
              size = 0;
          }
      
          //这里添加一个新的元素使用的是最小堆的操作,这里不详细说明了。
          void add(TimerTask task) {
              //如果数组已经存满任务,那么扩容一个新的数组为之前的两倍
              if (size + 1 == queue.length)
                  queue = Arrays.copyOf(queue, 2*queue.length);
              queue[++size] = task;
              fixUp(size);
          }
      
          private void fixUp(int k) {
              while (k > 1) {
                  int j = k >> 1;
                  if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                      break;
                  TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
                  k = j;
              }
          }
      }

    04.5 schedule发布任务

    • 当我们创建好Timer并且启动了循环线程以后,这个时候我们就需要发布任务。发布任务主要有以下几个方法。

      • schedule(TimerTask task, Date time)

        • 表示第一次执行任务的时间,时间间隔为0,也表示该任务只执行一次就结束了
      • schedule(TimerTask task, Date firstTime, long period)

        • firstTime 表示第一次执行的时间,period表示执行任务的时间间隔也就是多久时间执行一次
      • schedule(TimerTask task, long delay)

        • 延迟 delay时间执行任务,也就是在当前的时间+delay执行任务(该方法只执行一次任务)
    • 上面这三个方法都会执行sched方法,然后看一下这个

      • sched(TimerTask task, long time, long period)

        • 上面所有的执行任务的函数最后都是调用的该方法,task表示要执行的任务,time表示要执行任务的时间,period表示任务执行的间隔时间。
      • 具体看一下源代码

        private void sched(TimerTask task, long time, long period) {
            //如果时间间隔大于 long最大值的一般的话,需要对该数值 /2
            if (Math.abs(period) > (Long.MAX_VALUE >> 1))
                period >>= 1;
        
            synchronized(queue) {
                //首先判断轮训线程是否取消,如果取消状态直接抛出异常
                if (!thread.newTasksMayBeScheduled)
                    throw new IllegalStateException("Timer already cancelled.");
                synchronized(task.lock) {
                    //判断新执行的任务状态如果不是初始化状态话,直接抛出异常
                    if (task.state != TimerTask.VIRGIN)
                        throw new IllegalStateException("Task already scheduled or cancelled");
                    //赋值下次执行任务的时间
                    task.nextExecutionTime = time;
                    task.period = period;
                    //将任务状态修改为发布状态
                    task.state = TimerTask.SCHEDULED;
                }
                //将任务添加到最小堆队列中,注意:这里在添加到队列里面要保证第一个元素始终是最小的
                queue.add(task);
                //如果task就是队列中最小的任务话,则直接唤醒轮训线程执行任务(也就是唤醒TimerThread线程)
                if (queue.getMin() == task)
                    queue.notify();
            }
        }
      • 从上面的代码中可以清楚的明白发布任务非常简单的,就是往任务队列中添加任务然后判断条件是否需要唤醒轮训线程去执行任务。其核心代码是在 TimerThread 轮训中以及使用最小堆实现的队列保证每次取出来的第一个任务的执行时间是最小的。

    04.6 存在的问题分析

    • Timer通过一个寻轮线程循环的从队列中获取需要执行的任务,如果任务的执行时间未到则进行等待(通过Object类的 wait 方法实现阻塞等待)一段时间再自动唤醒执行任务。
    • 但是细心的我们发现这个是单线程执行的如果有多个任务需要执行的话会不会应付不过来呢?类似一个程序员,要开发多个需求,要是所有的事情所耗费的时间很短的话,那么就不会出现延迟问题,要是其中一件或者是某件事情非常耗时间的话那么则会影响到后面事情的时间。
    • 其实这个现象一样跟Timer出现的问题也是一样的道理,如果某个任务非常耗时间,而且任务队列中的任务又比较多的话,那 TimerThread 是忙不过来的,这样子就会导致后面的任务出现延迟执行的问题,进而会影响所有的定时任务的准确执行时间。
    • 那么有人就会想要可以一个TimerTask对应一个Timer不就行了吗?但是我们要清楚的明白计算机的系统资源是有限的,如果我们一个任务就去单独的开一个轮训线程执行的话,其实是有一点浪费系统的资源的,完全没有必要的,如果不需要定时任务了话,我们还需要去销毁线程释放资源的,如果是这样子的反复操作的话,不利于我们程序的流畅性。

    05.自定义倒计时器案例

    • 为了方便实现倒计时器自由灵活设置,且代码精简,能够适应一个页面创建多个定时器。或者用在列表中,同时倒计时器支持暂停,恢复倒计时等功能。这个就需要做特使处理呢。

      public class CountDownTimer {
      
          /**
           * 时间,即开始的时间,通俗来说就是倒计时总时间
           */
          private long mMillisInFuture;
          /**
           * 布尔值,表示计时器是否被取消
           * 只有调用cancel时才被设置为true
           */
          private boolean mCancelled = false;
          /**
           * 用户接收回调的时间间隔,一般是1秒
           */
          private long mCountdownInterval;
          /**
           * 记录暂停时候的时间
           */
          private long mStopTimeInFuture;
          /**
           * mas.what值
           */
          private static final int MSG = 520;
          /**
           * 暂停时,当时剩余时间
           */
          private long mCurrentMillisLeft;
          /**
           * 是否暂停
           * 只有当调用pause时,才设置为true
           */
          private boolean mPause = false;
          /**
           * 监听listener
           */
          private TimerListener mCountDownListener;
          /**
           * 是否创建开始
           */
          private boolean isStart;
      
          public CountDownTimer(){
              isStart = true;
          }
      
          public CountDownTimer(long millisInFuture, long countdownInterval) {
              long total = millisInFuture + 20;
              this.mMillisInFuture = total;
              //this.mMillisInFuture = millisInFuture;
              this.mCountdownInterval = countdownInterval;
              isStart = true;
          }
      
          /**
           * 开始倒计时,每次点击,都会重新开始
           */
          public synchronized final void start() {
              if (mMillisInFuture <= 0 && mCountdownInterval <= 0) {
                  throw new RuntimeException("you must set the millisInFuture > 0 or countdownInterval >0");
              }
              mCancelled = false;
              long elapsedRealtime = SystemClock.elapsedRealtime();
              mStopTimeInFuture = elapsedRealtime + mMillisInFuture;
              CountTimeTools.i("start → mMillisInFuture = " + mMillisInFuture + ", seconds = " + mMillisInFuture / 1000 );
              CountTimeTools.i("start → elapsedRealtime = " + elapsedRealtime + ", → mStopTimeInFuture = " + mStopTimeInFuture);
              mPause = false;
              mHandler.sendMessage(mHandler.obtainMessage(MSG));
              if (mCountDownListener!=null){
                  mCountDownListener.onStart();
              }
          }
      
          /**
           * 取消计时器
           */
          public synchronized final void cancel() {
              if (mHandler != null) {
                  //暂停
                  mPause = false;
                  mHandler.removeMessages(MSG);
                  //取消
                  mCancelled = true;
              }
          }
      
          /**
           * 按一下暂停,再按一下继续倒计时
           */
          public synchronized final void pause() {
              if (mHandler != null) {
                  if (mCancelled) {
                      return;
                  }
                  if (mCurrentMillisLeft < mCountdownInterval) {
                      return;
                  }
                  if (!mPause) {
                      mHandler.removeMessages(MSG);
                      mPause = true;
                  }
              }
          }
      
          /**
           * 恢复暂停,开始
           */
          public synchronized final  void resume() {
              if (mMillisInFuture <= 0 && mCountdownInterval <= 0) {
                  throw new RuntimeException("you must set the millisInFuture > 0 or countdownInterval >0");
              }
              if (mCancelled) {
                  return;
              }
              //剩余时长少于
              if (mCurrentMillisLeft < mCountdownInterval || !mPause) {
                  return;
              }
              mStopTimeInFuture = SystemClock.elapsedRealtime() + mCurrentMillisLeft;
              mHandler.sendMessage(mHandler.obtainMessage(MSG));
              mPause = false;
          }
      
      
          @SuppressLint("HandlerLeak")
          private Handler mHandler = new Handler() {
              @Override
              public void handleMessage(@NonNull Message msg) {
                  synchronized (CountDownTimer.this) {
                      if (mCancelled) {
                          return;
                      }
                      //剩余毫秒数
                      final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                      if (millisLeft <= 0) {
                          mCurrentMillisLeft = 0;
                          if (mCountDownListener != null) {
                              mCountDownListener.onFinish();
                              CountTimeTools.i("onFinish → millisLeft = " + millisLeft);
                          }
                      } else if (millisLeft < mCountdownInterval) {
                          mCurrentMillisLeft = 0;
                          CountTimeTools.i("handleMessage → millisLeft < mCountdownInterval !");
                          // 剩余时间小于一次时间间隔的时候,不再通知,只是延迟一下
                          sendMessageDelayed(obtainMessage(MSG), millisLeft);
                      } else {
                          //有多余的时间
                          long lastTickStart = SystemClock.elapsedRealtime();
                          CountTimeTools.i("before onTick → lastTickStart = " + lastTickStart);
                          CountTimeTools.i("before onTick → millisLeft = " + millisLeft + ", seconds = " + millisLeft / 1000 );
                          if (mCountDownListener != null) {
                              mCountDownListener.onTick(millisLeft);
                              CountTimeTools.i("after onTick → elapsedRealtime = " + SystemClock.elapsedRealtime());
                          }
                          mCurrentMillisLeft = millisLeft;
                          // 考虑用户的onTick需要花费时间,处理用户onTick执行的时间
                          // 打印这个delay时间,大概是997毫秒
                          long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
                          CountTimeTools.i("after onTick → delay1 = " + delay);
                          // 特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
                          // 注意,在onTick回调的方法中,不要做些耗时的操作
                          boolean isWhile = false;
                          while (delay < 0){
                              delay += mCountdownInterval;
                              isWhile = true;
                          }
                          if (isWhile){
                              CountTimeTools.i("after onTick执行超时 → delay2 = " + delay);
                          }
                          sendMessageDelayed(obtainMessage(MSG), delay);
                      }
                  }
              }
          };
      
          /**
           * 设置倒计时总时间
           * @param millisInFuture                    毫秒值
           */
          public void setMillisInFuture(long millisInFuture) {
              long total = millisInFuture + 20;
              this.mMillisInFuture = total;
          }
      
          /**
           * 设置倒计时间隔值
           * @param countdownInterval                 间隔,一般设置为1000毫秒
           */
          public void setCountdownInterval(long countdownInterval) {
              this.mCountdownInterval = countdownInterval;
          }
      
          /**
           * 设置倒计时监听
           * @param countDownListener                 listener
           */
          public void setCountDownListener(TimerListener countDownListener) {
              this.mCountDownListener = countDownListener;
          }
      
      }
    • 如何使用

      //开始
      mCountDownTimer.start();
      //结束销毁
      mCountDownTimer.cancel();
      //暂停
      mCountDownTimer.pause();
      //恢复暂停
      mCountDownTimer.resume();

    代码案例:https://github.com/yangchong211/YCTimer

    ]]>
    Dart语言基础Map、List、Set操作合辑-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 题记
    —— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。
    Dart语言基础Map、List、Set操作合辑@凡科快图.png

    Header1 Header2
    CSDN 网易云课堂教程
    掘金 EDU学院教程
    知乎 Flutter系列文章

    Flutter完全采用了Dart语言,所以分析Dart语言基础相当于是学习了Flutter语言基础、
    在Dart中,Map用来存储对像类型的数据,List与Set用来存储数组类型的数据。

    本文是来描述 Map 、List 、Set中数据的添加 、修改、循环遍历查询的。


    1 Dart中的Map

    Map用来保存key-value键值对的数据集合, 与Object-C中所说的字典一至,分为无序的HashMap、key的插入顺序的LinkedHashMap、按key的排序顺序的SplayTreeMap,Map的创建实例如下:

    // 创建一个Map实例,默认实现是LinkedHashMap。
    Map()
    
    // 创建一个LinkedHashMap实例,包含other中的所有键值对。
    Map.from(Map other)
    
    // 创建一个Map实例,其中Key和Value由iterable的元素计算得到。
    Map.fromIterable(Iterable iterable, {K key(element), V value(element)})
    
     // 将指定的keys和values关联,创建一个Map实例。
    Map.fromIterables(Iterable<K> keys, Iterable<V> values)
    
     // 使用默认实现LinkedHashMap创建一个严格的Map。
    Map.identity()
    
     // 创建一个不可修改、基于哈希值的Map,包含other所有的项
    Map.unmodifiable(Map other)

    然后在实际项目中结合数据创建Map实例,创建一个空的Map代码如下:

    // 创建一个Map实例, 插入顺序进行排列 默认无数据
      var dic = new Map();
      print(dic);  // {}
      // 创建一个空的Map, Map允许null作为key
      var dic5 = new Map.identity();
      print(dic5);  //{}
    
    

    创建有一个有初始值的Map,代码如下:

      // 根据一个Map创建一个新的Map, 插入顺序进行排列
      var dic1 = new Map.from({'name': '张三'});
      print(dic1);  // {name: 张三}
     
      // 根据List创建Map, 插入顺序进行排列
      List<int> list = [1, 2, 3];
      // 使用默认方式, key和value都是数组对应的元素
      var dic2 = new Map.fromIterable(list);
      print(dic2);  // {1: 1, 2: 2, 3: 3}
    
    
      // 设置key和value的值
      var dic3 = new Map.fromIterable(list, key: (item) => item.toString(), value: (item) => item * item);
      print(dic3);  // {1: 1, 2: 4, 3: 9}
    
      // 创建一个不可修改、基于哈希值的Map
      var dic6 = new Map.unmodifiable({'name': 张三});
      print(dic6); // {name: 张三}

    根据List数据来创建Map,代码如下:

      // 两个数组映射一个字典, 插入顺序进行排列
      List<String> keys = ['name', 'age'];
      var values = [张三, 20];
      // 如果有相同的key值, 后面的值会覆盖前面的值
      var dic4 = new Map.fromIterables(keys, values);
      print(dic4);  // {name: 张三, age: 20}

    对于Map来讲,初始化创建时可以赋值也可以是空的,当创建的可变的Map数据集合时,在实际开发中往往会根据不同的操作来修改不同的数据,如下:

      // 根据一个Map创建一个新的Map, 插入顺序进行排列
      // 在这里通过泛型指定 了 Map中的key的类型为 String类型 value是动态的
      Map<String, dynamic> dic1 = new Map.from({'name': '张三'});
      print(dic1);  // {name: 张三}
    
      //修改name的值 
      dic1['name'] = '李四';
      //向Map中添加新的键值对数据
      dic1['age'] = 23;

    然后获取Map中的数据如下操作;

    //根据key获取对应的值 
    String name = dic1= dic1['name'];
    
    ///遍历获取Map中所有的数据
    dic1.forEach((key, value) {
      print("${key} is ${value}");
    });
    

    2 Dart中的List

    对于List与Set来讲,都是用来存储数组类型数据,区别是Set不可保存重复数据,也就是说Set中的数据具有唯一性,在这里只分析List,Set与List的使用方法一至,使用代码如下:

    // 创建非固定长度的Lsit
    var testList = List();
    // 也可以 List testList = List();
    print(testList.length); // 0
    // 创建固定长度的List
    var fixedList = List(4);
    print(testList.length); // 4
     
     ///向Lsit中添加数据
    testList.add("hello");
    testList.add(123);
     
     
    // 创建元素类型固定的List
    var typeList = List<String>(); // 只能添加字符串类型的元素
     
    typeList.add("张三"); // 正确
    typeList.add(1); // 错误。类型不正确
     
    // 直接赋值 创建List
    var numList = [1, 2, 3];
    

    然后获取List中的数据需要通过索引来获取,List中保存的数据索引从0开始计数,代码如下:

    ///方式一 遍历获取List中的所有数据
    testList.forEach((value) {
      //value 就是List中对应的值
    });
    
    ///方式二 遍历获取List中的所有的数据
    for(int i=0;i<testList.length;i++){
      ///根据索引获取List中的数据
      var value = testList[i];
    }
    
    //方式三
    //while+iterator迭代器遍历,类似Java中的iteator       
    while(testList.iterator.moveNext()) {
      //获取对应的值
      var value = testList.iterator.current;
      
    }
    
    //方式四 增强for循环
    //for-in遍历       
    for (var value in testList) {
      //value 就是List中对应的值
    }
    

    完毕
    公众号 我的大前端生涯

    ]]>
    Java 开发必备! I/O与Netty原理精讲-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png

    一 Java I/O模型

    1 BIO(Blocking IO)

    BIO是同步阻塞模型,一个客户端连接对应一个处理线程。在BIO中,accept和read方法都是阻塞操作,如果没有连接请求,accept方法阻塞;如果无数据可读取,read方法阻塞。

    image.png

    2 NIO(Non Blocking IO)

    NIO是同步非阻塞模型,服务端的一个线程可以处理多个请求,客户端发送的连接请求注册在多路复用器Selector上,服务端线程通过轮询多路复用器查看是否有IO请求,有则进行处理。

    image.png

    NIO的三大核心组件:

    Buffer:用于存储数据,底层基于数组实现,针对8种基本类型提供了对应的缓冲区类。

    Channel:用于进行数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从Buffer写到Channel中。

    Selector:选择器,当向一个Selector中注册Channel后,Selector 内部的机制就可以自动不断地查询(Select)这些注册的Channel是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个Channel,也可以说管理多个网络连接,因此,Selector也被称为多路复用器。当某个Channel上面发生了读或者写事件,这个Channel就处于就绪状态,会被Selector监听到,然后通过SelectionKeys可以获取就绪Channel的集合,进行后续的I/O操作。

    image.png

    Epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

    3 AIO(NIO 2.0)

    AIO是异步非阻塞模型,一般用于连接数较多且连接时间较长的应用,在读写事件完成后由回调服务去通知程序启动线程进行处理。与NIO不同,当进行读写操作时,只需直接调用read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。

    二 I/O模型演化

    1 传统I/O模型

    对于传统的I/O通信方式来说,客户端连接到服务端,服务端接收客户端请求并响应的流程为:读取 -> 解码 -> 应用处理 -> 编码 -> 发送结果。服务端为每一个客户端连接新建一个线程,建立通道,从而处理后续的请求,也就是BIO的方式。

    image.png

    这种方式在客户端数量不断增加的情况下,对于连接和请求的响应会急剧下降,并且占用太多线程浪费资源,线程数量也不是没有上限的,会遇到各种瓶颈。虽然可以使用线程池进行优化,但是依然有诸多问题,比如在线程池中所有线程都在处理请求时,无法响应其他的客户端连接,每个客户端依旧需要专门的服务端线程来服务,即使此时客户端无请求,也处于阻塞状态无法释放。基于此,提出了基于事件驱动的Reactor模型。

    2 Reactor模型

    Reactor模式是基于事件驱动开发的,服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor模式也叫Dispatcher模式,即I/O多路复用统一监听事件,收到事件后分发(Dispatch给某进程),这是编写高性能网络服务器的必备技术之一。

    Reactor模式以NIO为底层支持,核心组成部分包括Reactor和Handler:

    • Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对I/O事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人。
    • Handlers:处理程序执行I/O事件要完成的实际事件,Reactor通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。类似于客户想要与之交谈的公司中的实际员工。

    根据Reactor的数量和Handler线程数量,可以将Reactor分为三种模型:

    • 单线程模型 (单Reactor单线程)
    • 多线程模型 (单Reactor多线程)
    • 主从多线程模型 (多Reactor多线程)

    单线程模型

    image.png

    Reactor内部通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立的事件,则由Acceptor处理,Acceptor通过accept接受连接,并创建一个Handler来处理连接后续的各种事件,如果是读写事件,直接调用连接对应的Handler来处理。

    Handler完成read -> (decode -> compute -> encode) ->send的业务流程。

    这种模型好处是简单,坏处却很明显,当某个Handler阻塞时,会导致其他客户端的handler和accpetor都得不到执行,无法做到高性能,只适用于业务处理非常快速的场景,如redis读写操作。

    多线程模型

    image.png

    主线程中,Reactor对象通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立事件,则由Acceptor处理,Acceptor通过accept接收连接,并创建一个Handler来处理后续事件,而Handler只负责响应事件,不进行业务操作,也就是只进行read读取数据和write写出数据,业务处理交给一个线程池进行处理。

    线程池分配一个线程完成真正的业务处理,然后将响应结果交给主进程的Handler处理,Handler将结果send给client。

    单Reactor承担所有事件的监听和响应,而当我们的服务端遇到大量的客户端同时进行连接,或者在请求连接时执行一些耗时操作,比如身份认证,权限检查等,这种瞬时的高并发就容易成为性能瓶颈。

    主从多线程模型

    image.png

    存在多个Reactor,每个Reactor都有自己的Selector选择器,线程和dispatch。

    主线程中的mainReactor通过自己的Selector监控连接建立事件,收到事件后通过Accpetor接收,将新的连接分配给某个子线程。

    子线程中的subReactor将mainReactor分配的连接加入连接队列中通过自己的Selector进行监听,并创建一个Handler用于处理后续事件。

    Handler完成read -> 业务处理 -> send的完整业务流程。

    关于Reactor,最权威的资料应该是Doug Lea大神的Scalable IO in Java,有兴趣的同学可以看看。

    三 Netty线程模型

    Netty线程模型就是Reactor模式的一个实现,如下图所示:

    image.png

    1 线程组

    Netty抽象了两组线程池BossGroup和WorkerGroup,其类型都是NioEventLoopGroup,BossGroup用来接受客户端发来的连接,WorkerGroup则负责对完成TCP三次握手的连接进行处理。

    NioEventLoopGroup里面包含了多个NioEventLoop,管理NioEventLoop的生命周期。每个NioEventLoop中包含了一个NIO Selector、一个队列、一个线程;其中线程用来做轮询注册到Selector上的Channel的读写事件和对投递到队列里面的事件进行处理。

    Boss NioEventLoop线程的执行步骤:

    • 处理accept事件, 与client建立连接, 生成NioSocketChannel。
    • 将NioSocketChannel注册到某个worker NIOEventLoop上的selector。
    • 处理任务队列的任务, 即runAllTasks。

    Worker NioEventLoop线程的执行步骤:

    • 轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。
    • 处理read和write事件,在对应NioSocketChannel处理业务。
    • runAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理,这样不影响数据在pipeline中的流动处理。

    Worker NIOEventLoop处理NioSocketChannel业务时,使用了pipeline (管道),管道中维护了handler处理器链表,用来处理channel中的数据。

    2 ChannelPipeline

    Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的双向链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。

    ChannelPipeline是一系列的ChannelHandler实例,流经一个Channel的入站和出站事件可以被ChannelPipeline 拦截。每当一个新的Channel被创建了,都会建立一个新的ChannelPipeline并绑定到该Channel上,这个关联是永久性的;Channel既不能附上另一个ChannelPipeline也不能分离当前这个。这些都由Netty负责完成,而无需开发人员的特别处理。

    根据起源,一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传播到下一个ChannelHandler。一个ChannelHandler处理程序可以通知ChannelPipeline中的下一个ChannelHandler执行。Read事件(入站事件)和write事件(出站事件)使用相同的pipeline,入站事件会从链表head 往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

    image.png

    ChannelInboundHandler回调方法:

    image.png

    ChannelOutboundHandler回调方法:

    image.png

    3 异步非阻塞

    写操作:通过NioSocketChannel的write方法向连接里面写入数据时候是非阻塞的,马上会返回,即使调用写入的线程是我们的业务线程。Netty通过在ChannelPipeline中判断调用NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果发现不是则会把写入请求封装为WriteTask投递到其对应的NioEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件时候,将其从队列里面取出来执行。

    读操作:当从NioSocketChannel中读取数据时候,并不是需要业务线程阻塞等待,而是等NioEventLoop中的IO轮询线程发现Selector上有数据就绪时,通过事件通知方式来通知业务数据已就绪,可以来读取并处理了。

    每个NioSocketChannel对应的读写事件都是在其对应的NioEventLoop管理的单线程内执行,对同一个NioSocketChannel不存在并发读写,所以无需加锁处理。

    使用Netty框架进行网络通信时,当我们发起I/O请求后会马上返回,而不会阻塞我们的业务调用线程;如果想要获取请求的响应结果,也不需要业务调用线程使用阻塞的方式来等待,而是当响应结果出来的时候,使用I/O线程异步通知业务的方式,所以在整个请求 -> 响应过程中业务线程不会由于阻塞等待而不能干其他事情。

    ]]>
    物模型接入价值与实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 物模型价值

    物联网元年

    关键词:探索、快速

    2016年阿里云物联网平台(前称:物联网套件)上线,为客户设备上云提供了通道能力,包括MQTT连接、消息流转等核心功能。
    第一批客户大多基于该模式使用物联网平台能力,当时整个行业处于物联网云平台起步期,包括AWS,Azure起步阶段同样只是提供通道能力。
    基于通道能力,客户使用物联网平台接入方式详见文档 https://developer.aliyun.com/article/746536
    这个阶段的客户大多是硬件厂商,软硬一体开发,尝试物联网转型提升设备价值,对物联网平台的诉求比较简单,希望自己更多参与,对新模式有更多把控力,所以都会采用自定义协议上云。

    test

    物联网繁荣

    关键词:生态、扩展、数字化

    近两年物联网设备、解决方案如雨后春笋般涌出,不少用户希望赶上物联网这波浪潮。这个阶段的客户不仅仅关注设备连云,也开始关注围绕设备产生的解决方案。因此客户角色从硬件厂商,快速扩展到集成商、软件提供商等。由于大量角色的进入,对软硬开发解耦、易扩展的能力提出了诉求。同时我们也发现第一批使用通道能力的平台客户随着自己业务发展、设备扩展,原来的架构已无法支撑,对物联网平台也提出了新的要求。

    举两个典型场景:

    • 老客户升级:某个共享设备提供商,原来仅提供大学校园共享洗衣机服务,利用物联网平台通道能力上云,随着公司业务发展,从共享洗衣机业务扩展到校园淋浴、饮水机、充电桩等多类设备,原来自定义协议和API无法支撑多品类设备,难扩展。需要有一套接入标准和规范,方便快速扩展设备类型。
    • 新生态客户:某个充电桩平台客户,提供充电桩管理平台,作为甲方要求大量桩企(乙方)按照平台规范接入,典型的软硬件分离场景。需要有一套接入标准和规范,方便快速扩展桩企规模。

    这一阶段平台在通道能力之上,提供了物模型能力,物模型可以屏蔽底层设备差异,让软件开发者基于平台提供的标准API开发;硬件开发者基于平台提供的标准协议开发;从而达到软硬开发解耦的目的。

    test

    物联网赋能

    关键词:场景化、智能

    物联网终极目标一定是基于设备采集数据赋能业务,实现数字业务化。例如金融、物流、家居、餐饮、商场、医疗、交通等不同领域通过物联网数字化后,结合数据分析智能化决策、互联互通、场景规则、数字孪生等能力实现纵深领域场景化、智能化。
    这一阶段平台在通道能力、物模型能力之上,还进一步提供设备智能运维、数据分析、可视化、数字孪生等高价值服务,帮助客户数字化后产生真正的业务价值。

    test

    基于以上分析,物联网已经过了最初的“元年”阶段,也迈入了“繁荣”阶段,正逐步朝“问物联网要赋能”的阶段演进。物模型是物联网生态化、高扩展、数字化、智能化非常重要的基础,强烈建议客户使用。

    物模型接入实践

    自定义接入模式

    以一个老客户为例,原来仅使用物联网平台通道能力,下图中1~8流程都需要自定义开发,当客户设备类型足够简单时,该模式复杂度通常不会成为客户痛点。

    test

    面临的挑战

    随着客户接入设备种类越来越多,面临的扩展性问题也越来越严峻。

    test

    使用物模型后的模式

    物模型模式下,设备与云交互协议、云平台设备API都基于物模型标准化了,即使设备不断扩展,客户业务服务器和设备端逻辑都不需要进行调整,保证了扩展性。

    test

    物模型接入流程详细介绍

    流程图

    以下是客户详细接入流程,主要分为:云端配置、设备开发、服务端开发、设备运行时管理四大部分。平台会提供一些工具,使各部分流程更高效。接下来进行详细介绍。
    image.png
    本文试图手把手介绍从0到1接入物模型,还会配套介绍一些接入过程中有帮助的平台能力,所以文章篇幅比较长,事实上客户接入流程还是非常简单的,真正开发只需要涉及到图中红色三个模块。

    如果您希望快速接入,可以直接关注P0部分,其它部分都可以跳过。

    1 云端配置

    1.1 创建产品(P0)

    1.登录物联网平台
    2.创建产品。
    image.png
    说明
    • 所属品类:标准品类库提供了一些供参考的模板,选择后可以修改,建议使用。
    • 节点类型:根据实际选择即可。
    • 数据格式:“ICA标准数据格式(Alink JSON)”表示设备使用标准Alink JSON格式上报数据;“透传/自定义”表示设备可以使用自定义格式,通过Alink特定Topic上报物联网平台,该模式客户需要写脚本进行转换,透传模式在此不做展开,后面单独起文章介绍。

    1.2 物模型建模(P0)

    1.模型查看。
    已有的模型是继承自创建产品时选择的“充电桩”品类模板。
    image.png

    2.编辑模型。
    通过“编辑草稿”,进行修改和添加,最后需要对物模型“发布上线”。
    image.png
    说明
    • 定义物模型非常重要,物模型通过属性、事件、服务三要素描述了设备所有能力,设备和云交互、客户服务器访问设备通过物模型都可以实现协议标准化。如果客户定义的物模型如果足够通用和专业,阿里可以帮助作为ICA行业标准进行推广。
    • 服务的调用方式有:同步调用、异步调用两种模式。客户云端开发调用下行控制API,同步调用和异步调用获取返回结果方式不一样,在后文“3.3”章节详细介绍。

    物模型概念介绍
    物模型介绍文档请参见这里
    了解物模型概念,能够帮助您更好对设备建模。

    1.3 物模型配置

    当前默认是物模型强校验模式,即设备上报数据在IoT平台会进行物模型数据规范强校验,如果不符合规范会报错。
    另外物模型弱校验、免校验、去重等规则也会在近期陆续开放,后期进行文档补充。
    配置之后,会在设备运行时生效。

    关联阅读:4.2 物模型扩展规则校验。

    1.4 注册三元组(P0)

    1.注册设备。
    image.png
    说明
    • 添加设备:测试阶段使用较多,单个添加。
    • 批量添加:量产阶段使用,有两种模式,“自动生成”表示设备标识符(deviceName)由平台按照一定的规则随机颁发;“批量上传”支持客户自定义设备标识符(deviceName)。
    2.查看设备列表。
    可以通过“设备列表”、“批次管理”两种方式查看创建的设备列表。
    image.png
    通过“批次管理”查看这一批次设备详情,并且支持下载三元组列表。
    image.png

    注意:此处设备标识符(deviceName)非常重要,与productKey, deviceSecret一起称为设备的“三元组”,作为设备的唯一身份,大部分情况需要烧录到设备上。

    2 设备开发

    2.1 使用设备SDK开发(P0)

    设备接入SDK文档请参见这里
    image.png

    根据需要选择合适的语言版本。C SDK 建议使用“4.x”版本。

    本文选择 Java SDK进行演示。
    环境准备:https://help.aliyun.com/document_detail/97331.html
    物模型开发:https://help.aliyun.com/document_detail/97333.html

    1.开发之前需要先准备如下好两份数据:

    • 设备证书信息(productKey、deviceName、deviceSecret)
      image.png
    • 设备物模型
      image.png

    为了方便查看物模型详细数据规范,通过导出“物模型TSL”查看详细物模型定义,其中包括物模型属性、事件、服务标识符、参数、数据规范。抽取部分内容,针对以下属性、事件、服务在DEMO中进行开发演示。

        "schema":"https://iotx-tsl.oss-ap-southeast-1.aliyuncs.com/schema.json",
        "profile":{
            "productKey":"a1nhbEV****"
        },
        "properties":[
            {
                "identifier":"acOutMeterIty",
                "name":"交流输出电表底值监测属性",
                "accessMode":"rw",
                "required":false,
                "dataType":{
                    "type":"int",
                    "specs":{
                        "min":"0",
                        "max":"200",
                        "step":"1"
                    }
                }
            }
        ],
        "events":[
            {
                "identifier":"post",
                "name":"post",
                "type":"info",
                "required":true,
                "desc":"属性上报",
                "method":"thing.event.property.post",
                "outputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ]
            },
            {
                "identifier":"startChaResEvt",
                "name":"启动充电结果事件",
                "type":"info",
                "required":false,
                "method":"thing.event.startChaResEvt.post",
                "outputData":[
                    {
                        "identifier":"gunNum",
                        "name":"充电枪编号",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ]
            }
        ],
        "services":[
            {
                "identifier":"set",
                "name":"set",
                "required":true,
                "callType":"async",
                "desc":"属性设置",
                "method":"thing.service.property.set",
                "inputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ],
                "outputData":[
    
                ]
            },
            {
                "identifier":"get",
                "name":"get",
                "required":true,
                "callType":"async",
                "desc":"属性获取",
                "method":"thing.service.property.get",
                "inputData":[
                    "acOutMeterIty"
                ],
                "outputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ]
            },
            {
                "identifier":"startChaResService",
                "name":"开启充电",
                "required":false,
                "callType":"async",
                "method":"thing.service.startChaResService",
                "inputData":[
                    {
                        "identifier":"charm",
                        "name":"电量",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"1",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ],
                "outputData":[
                    {
                        "identifier":"realcharm",
                        "name":"realcharm",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ]
            }
        ]
    }

    2.开发代码。
    如下示例中只需要将三元组,和属性、事件、服务参数替换成您的设备信息。其它代码可以直接运行。

    关于免订阅能力介绍:

    有些设备最资源比较敏感,为了避免初始化订阅大量Alink协议中系统Topic带来的性能开销,平台提供了免订阅能力,即平台帮设备进行Topic订阅。
    SDK只有3.1.0及以后版本支持免订阅能力,并且默认打开该能力。
    如果3.1.0及以后版本SDK您希望取消免订阅,依旧按需订阅Topic,可以设置SDK配置项关闭该能力,在make.settings中设置“FEATURE_MQTT_AUTO_SUBSCRIBE=n”。

    public class Demo {
    
        public static void main(String[] args) throws Exception {
    
            String pk = "a1nhbEVCP**";
            String dn = "7mBP6Dd6IT27Rt***";
            String ds = "*****";
    
            /**
             * 连接 & 认证
             */
            LinkKitInitParams params = new LinkKitInitParams();
    
            // 设置 Mqtt 初始化参数
            IoTMqttClientConfig config = new IoTMqttClientConfig();
            config.productKey = pk;
            config.deviceName = dn;
            config.deviceSecret = ds;
            config.receiveOfflineMsg = false;
            params.mqttClientConfig = config;
    
            // 设置初始化三元组信息,用户传入
            DeviceInfo deviceInfo = new DeviceInfo();
            deviceInfo.productKey = pk;
            deviceInfo.deviceName = dn;
            deviceInfo.deviceSecret = ds;
    
            params.deviceInfo = deviceInfo;
    
            LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
                public void onError(AError aError) {
                    System.out.println("===============FAILURE===============");
                    ALog.e(TAG, "Init Error error=" + aError);
                    System.out.println("===============FAILURE===============");
                }
    
                public void onInitDone(InitResult initResult) {
                    System.out.println("===============SUCCESS===============");
                    ALog.i(TAG, "onInitDone result=" + initResult);
                    System.out.println("===============SUCCESS===============");
                }
    
            });
    
            //此处sleep 5S,由于上面init是异步流程
            Thread.sleep(5000);
    
            /**
             * 物模型开发
             */
    
            /**
             * 上报属性
             */
            Map<String, ValueWrapper> properties = new HashMap<>();
    
            // key为物模型中属性标识符"acOutMeterIty",value需要遵循属性值规范:int类型,取值范围在0~200之间;
            properties.put("acOutMeterIty", new ValueWrapper(10));
    
            LinkKit.getInstance().getDeviceThing().thingPropertyPost(properties, new IPublishResourceListener() {
    
                @Override
                public void onSuccess(String s, Object o) {
                    System.out.println("=====thingPropertyPost success=======");
                    System.out.println(s);
                    System.out.println(JSON.toJSONString(o));
                }
    
                @Override
                public void onError(String s, AError aError) {
                    System.out.println("=====thingPropertyPost failure=======");
                }
            });
    
            // 上报属性之后,云端会返回响应结果,此处是监听云端返回的属性reply
            LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
    
                @Override
                public void onNotify(String s, String s1, AMessage aMessage) {
                    System.out.println("===PROPERTY REPLY===");
                    System.out.println("TOPIC:" + s1);
                    System.out.println("Payload:" + JSON.toJSONString(aMessage));
                }
    
                @Override
                public boolean shouldHandle(String s, String s1) {
                    return false;
                }
    
                @Override
                public void onConnectStateChange(String s, ConnectState connectState) {
                }
            });
    
            /**
             * 上报事件
             */
            HashMap<String, ValueWrapper> eventMap = new HashMap<>();
    
            // key为物模型中事件参数的标识符"gunNum", value为事件参数值需要遵循数值规范:int类型,取值范围0~100之间;
            eventMap.put("gunNum", new ValueWrapper.IntValueWrapper(50));
    
            OutputParams eventOutput = new OutputParams(eventMap);
    
            // 参数identity为"startChaResEvt"属于物模型事件标识符。
            LinkKit.getInstance().getDeviceThing().thingEventPost("startChaResEvt", eventOutput, new IPublishResourceListener() {
                public void onSuccess(String resId, Object o) {
                    System.out.println("=====thingEventPost success=======");
                    System.out.println(resId);
                    System.out.println(JSON.toJSONString(o));
                }
    
                public void onError(String resId, AError aError) {
                    System.out.println("=====thingEventPost failure=======");
                }
            });
    
            /**
             * 监听并执行下行服务
             */
            // 获取设备支持的所有服务
            LinkKit.getInstance().getDeviceThing().getServices();
    
            // 用户可以根据实际情况注册自己需要的服务的监听器
            List<Service> srviceList = LinkKit.getInstance().getDeviceThing().getServices();
    
            for (int i = 0; srviceList != null && i < srviceList.size(); i++) {
                Service service = srviceList.get(i);
    
                LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), new ITResRequestHandler() {
    
                    public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
    
                        System.out.println("onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
                        System.out.println("收到云端异步服务调用 " + identify);
                        try {
                            /**
                             * 设置属性(property)的模式
                             */
                            // "set"为设置属性默认的标识符
                            if ("set".equals(identify)) {
                                // TODO 用户需要设置真实设备的的属性
                                /**
                                 * 向云端同步设置好的属性值
                                 */
                                Map<String, ValueWrapper> desiredProperty = (Map<String, ValueWrapper>) ((InputParams) result).getData();
    
                                LinkKit.getInstance().getDeviceThing().thingPropertyPost(desiredProperty, new IPublishResourceListener() {
    
                                    @Override
                                    public void onSuccess(String s, Object o) {
                                        if (result instanceof InputParams) {
                                            Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                            //                        data.get()
                                            ALog.d(TAG, "收到异步下行数据 " + data);
                                            // 响应云端 接收数据成功
                                            itResResponseCallback.onComplete(identify, null, null);
                                        } else {
                                            itResResponseCallback.onComplete(identify, null, null);
                                        }
                                    }
    
                                    @Override
                                    public void onError(String s, AError aError) {
                                        AError error = new AError();
                                        error.setCode(100);
                                        error.setMsg("setPropertyFailed.");
                                        itResResponseCallback.onComplete(identify, new ErrorInfo(error), null);
                                    }
                                });
    
                                /**
                                 * 服务(service)的模式
                                 */
                                // "startChaResService"为服务的标识符
                            } else if ("startChaResService".equals(identify)) {
    
                                Map<String, ValueWrapper> inputParams = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                // TODO 根据服务入参inputParams执行设备逻辑,比如启动充电
                                // 充电完成后,向云端返回输出参数
                                OutputParams outputParams = new OutputParams();
                                // key为"charm"属于物模型中"startChaResService"服务出参标识符,value为出参值遵循数据规范:int类型,数据范围1~100之间;
                                outputParams.put("charm", new ValueWrapper.IntValueWrapper(20));
    
                                itResResponseCallback.onComplete(identify, null, outputParams);
    
                            } else {
                                // 根据不同的服务做不同的处理,跟具体的服务有关系
                                OutputParams outputParams = new OutputParams();
                                // 根据特定服务,按照服务规范返回服务的出参。
                                itResResponseCallback.onComplete(identify, null, outputParams);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            ALog.d(TAG, "云端返回数据格式异常");
                        }
                    }
                    public void onSuccess(Object o, OutputParams outputParams) {
                        ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
                        ALog.d(TAG, "注册服务成功");
                    }
                    public void onFail(Object o, ErrorInfo errorInfo) {
                        ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
                        ALog.d(TAG, "注册服务失败");
                    }
                });
            }
        }
    }

    说明
    • 上报属性成功,云端会返回REPLY,有以下日志说明设备到云,云到设备的链路全部走通。
    image.png
    • 设备收到属性设置指令,在完成物理设备属性修改后,建议将最新属性同步上报云端。

    2.2 不使用SDK开发

    1.协议准备。
    “2.1 使用设备SDK开发”介绍了使用阿里云提供的SDK进行设备开发,当然您也可以选择不使用SDK,完全基于Alink协议(设备和云交互协议)开发。
    Alink协议文档:https://help.aliyun.com/document_detail/90459.html
    重点关注物模型协议部分:https://help.aliyun.com/document_detail/89301.html 。里面包含了物模型相关所有Topic介绍(物模型Topic列表在控制台也可以查看,如下图)。

    test

    文档详细介绍了设备端如何向云端上报“属性”、“事件”,如何订阅云端向下发送的“服务”指令。
    Topic和Payload都基于客户定义的物模型进行标准化和规范化,从而使得客户设备与云交互方式不会随着设备类型变化而改变,满足扩展性要求。
    image.png

    2.环境准备。
    根据自己选型选择合适的MQTT客户端,本文选择eclipse paho。

    <dependency>
        <groupId>org.eclipse.paho</groupId>
        <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
        <version>1.1.1</version>//可以选择您需要的版本
    </dependency>

    3.开发。
    物模型复用“2.1 使用设备SDK开发”中“开发前准备”给出的。

    关于免订阅能力介绍:

    有些设备最资源比较敏感,为了避免初始化订阅大量Alink协议中系统Topic带来的性能开销,平台提供了免订阅能力,即平台帮设备进行Topic订阅。
    SDK只有3.1.0及以后版本支持免订阅能力,并且默认打开该能力。
    如果不使用SDK开发,可以通过设备端在MQTT的连接报文中的clientId部分, 新增_ss=1表示开启自动订阅, 建连成功后服务端会自动订阅上以下表格中的topic, 若传递 _ss=0 或者不传递该字段, 则不会发生服务端自动订阅动作。

    4.上报属性。

    String productKey = "a1nhbEV****";
    String deviceName = "7mBP6Dd6IT2*****";
    String deviceSecret = "****";
    
    // MQTT连接
    MqttTestClient client;
    client = new MqttTestClient(productKey, deviceName, deviceSecret);
    
    client.connect();
    
    String setTopic = "/thing/event/property/post";
    String setTopicReply = "/thing/event/property/post_reply";
    
    // 上报属性,云端会返回REPLY,进行订阅。(为了节省端侧订阅开销,可以开通免订阅)
    // 此处client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client subscribe
    client.sysTopic(setTopicReply).subscribe();
    
    // 封装Alink协议系统参数
    Map<String, Object> payload = new HashMap<String, Object>();
    Map<String, Object> params = new HashMap<String, Object>();
    payload.put("id", 11);//id需要保证设备端一段时间内唯一
    payload.put("params", params);
    payload.put("method", "thing.event.property.post");
    
    // 组装属性payload
    String propKey = "acOutMeterIty";
    int statusValue = 30;
    Map<String, Object> proValue = new HashMap<>();
    proValue.put("value", statusValue);
    proValue.put("time", System.currentTimeMillis());
    params.put(propKey, proValue);
    
    // 上报(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client publish消息)
    client.sysTopic(setTopic).publish(JSON.toJSONString(payload));
    
    // 打印云端返回的Reply(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client监听订阅消息)
    client.sysTopic(setTopicReply).readTopic(10000);
    
    client.disconnect();

    日志打印的设备请求和响应。
    image.png

    5.上报事件。

    
    String productKey = "a1nhbEV****";
    String deviceName = "7mBP6Dd6IT27*****";
    String deviceSecret = "***";
    
    // MQTT连接
    MqttTestClient client;
    client = new MqttTestClient(productKey, deviceName, deviceSecret);
    
    client.connect();
    
    // topic中为"startChaResEvt"属于物模型事件标识符。
    String setTopic = "/thing/event/startChaResEvt/post";
    String setTopicReply = "/thing/event/startChaResEvt/post_reply";
    
    // 报事件,云端会返回REPLY,进行订阅。(为了节省端侧订阅开销,可以开通免订阅)
    client.sysTopic(setTopicReply).subscribe();
    
    // 封装Alink协议系统参数
    Map<String, Object> payload = new HashMap<String, Object>();
    Map<String, Object> params = new HashMap<String, Object>();
    payload.put("id", 11);//id需要保证设备端一段时间内唯一
    payload.put("params", params);
    payload.put("method", "thing.event.startChaResEvt.post");
    
    // 组装属性payload
    Map<String, Object> dataValue = new HashMap<>();
    // key为物模型中事件参数的标识符"gunNum", value为事件参数值需要遵循数值规范:int类型,取值范围0~100之间;
    dataValue.put("gunNum", 59);
    
    params.put("value", dataValue);
    params.put("time", System.currentTimeMillis());
    
    // 上报(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client publish消息)
    client.sysTopic(setTopic).publish(JSON.toJSONString(payload));
    
    // 打印云端返回的Reply(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client监听订阅消息)
    client.sysTopic(setTopicReply).readTopic(10000);
    
    client.disconnect();

    6.服务调用。
    此处为一段伪代码。可以在MQTT建连的时候通过callback监听云端下发的控制指令或消息。
    前提:已经对下行的TOPIC进行订阅过,免订阅能力参考上面介绍。

    mqttClient = new MqttClient(url, clientId, persistence);
    final MqttConnectOptions connOpts = new MqttConnectOptions();
    connOpts.setMqttVersion(4);
    connOpts.setAutomaticReconnect(true);
    connOpts.setCleanSession(false);
    connOpts.setUserName(mqttUsername);
    connOpts.setPassword(mqttPassword.toCharArray());
    connOpts.setKeepAliveInterval(65);
    LogUtil.log(clientId + "进行连接, 目的地: " + url);
    
    // 此处订阅云端下发的消息
    mqttClient.setCallback(new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
            LogUtil.log("connection lost, cause:" + cause);
            cause.printStackTrace();
        }
    
        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            TopicChannel topicChannel = getTopic(topic);
            LogUtil.log("receive message, channel:" + topicChannel
                        + ",topic:" + topic
                        + ", payload:" + new String(message.getPayload(), "UTF-8") + "");
            topicChannel.put(message);
        }
    
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
            //如果是qos 0消息 token.resp是没有回复的
            LogUtil.log("sent, " + ((token == null || token.getResponse() == null) ? "null"
                                    : token.getResponse().getKey()));
        }
    });
    
    mqttClient.connect(connOpts);

    重点说明
    • 所有被订阅的下行Topic都会被监听到。物模型相关的主要包括:属性上报Reply、属性下行设置、服务下行控制。
    • 设置设备属性(https://help.aliyun.com/document_detail/89301.html#title-wmh-y2e-18r),默认异步方式返回结果。
    • 订阅的Topic为Alink协议标准Topic:“/sys/{productKey}/{deviceName}/thing/service/property/set”
    • 服务控制(https://help.aliyun.com/document_detail/89301.html#title-3pt-nfy-jys),同异步方式取决于物模型中service配置的调用模式。
    • 服务异步方式订阅的Topic为Alink协议标准Topic:“/sys/{productKey}/{deviceName}/thing/service/{tsl.service.identifier}”
    • 服务同步方式订阅的Topic需要遵循RRPC Topic模式:详见文档https://help.aliyun.com/document_detail/90568.html

    注意:仅设备侧需要感知RRPC特殊TOPIC,设备上云后,数据流转、开放API面向的还是Alink协议编程。

    2.3 在线调试

    设备开发后之后,如何快速模拟业务服务器给设备下发指令,调试设备能力?IoT平台提供了“在线调试”的功能,可以模拟设备或模拟应用端到端调试。

    test

    此处使用“在线调试”里面“调试真实设备”能力。通过控制台下发设备控制指令,分两类:1)属性设置;2)服务调用。

    1.服务调用调试。

    image.png

    image.png
    云端下发后,可以到设备端查看控制Log是否打印,以判断指令达到端侧。
    从图中可见设备收到startChaResService服务,同时向云端返回了输出参数。

    2.属性设置调试。

    image.png
    说明
    • “获取”:暂不支持到设备,只能从云端获取设备最新属性。
    • “设置”:指令直接到设备端,设备修改本地属性之后,上报云端最新属性;到设备上的设置指令为"set"。
    • “设置期望值”:如果设备在线,会直接下发设备,如果设备离线,指令在云端进行缓存,待上线后下发设备端,下发之后,设备修改本地属性之后,同样上报云端最新属性;到设备上的设置指令同样为"set"。如果您希望使用物模型期望值能力,可点击查看最佳实践。
    image.png
    云端下发后,可以到设备端查看控制Log是否打印,以判断指令达到端侧。
    从图中可见设备收到set指令,返回了服务响应,同时向云端上报了最新属性。

    说明:服务结果还可以通过“2.4 查看物模型数据”章节中获取。

    2.4 查看物模型数据

    DEMO运行之后,可以看到设备已经“在线”状态。
    “运行状态”展示设备上报的属性值;
    “事件管理”展示设备上报的事件;
    “服务调用”展示云端下发设备的控制服务;
    image.png

    上报属性结构化展示。
    image.png

    上报事件,包括事件参数展示。
    image.png

    属性设置、服务调用两类服务的云端下发入参、设备响应出参都有展示,如上证明设备收到云端指令,并且正常返回响应。

    2.5 查看日志服务

    设备在运行过程,可能会出现一些异常,比如连接失败、认证失败、数据异常等等,为了便于排查,可以查看日志服务。举例设备上报数据可能会不符合物模型规范,比如事件参数"gunNum"对应值的数据范围为0~100之间,而真实上报了50000。日志服务会展示设备错误详情。
    image.png

    image.png
    可以看到日志内容为“{"Reason":"tsl parse: int value is bigger than max 100 -> gunNum"}”,说明gunNum对应值超过物模型规范最大值100的限制。物模型规范详情到“物模型TSL”查看。

    image.png
    同时可以通过“日志转储”中“日志报表”进一步查看设备大盘,包括设备上下线次数、设备上线IP区域分布、设备消息量、设备消息量Top列表、物模型错误分布、云端API错误分布等多维度指标。
    日志服务介绍文档请参见这里

    3 服务端开发

    设备连接到阿里云IoT平台,设备数据会保存在IoT平台时序数据库。同时IoT平台提供两种方式供客户获取设备数据:方式1)通过服务端订阅或者规则引擎实时流转到客户服务器;2)通过开放API供客户调用获取。

    3.1 服务端调用API开发(P0)

    1.环境准备。
    SDK下载文档:https://help.aliyun.com/document_detail/30581.html
    API接口列表:https://help.aliyun.com/document_detail/69579.html
    重点关注物模型使用相关API

    test

    2.以下示例为设置设备属性API,设备异步返回结果,客户需要通过“数据流转”方式获取。

    String accessKey = "***";
    String accessSecret = "***";
    try {
        DefaultProfile.addEndpoint("cn-shanghai", "cn-shanghai", "Iot", "iot.cn-shanghai.aliyuncs.com");
    } catch (Exception e) {
        System.out.println("DefaultProfile exception");
    }
    
    IClientProfile profile = DefaultProfile.getProfile("cn-shanghai", accessKey, accessSecret);
    DefaultAcsClient defaultAcsClient = new DefaultAcsClient(profile);
    
    SetDevicePropertyRequest setDevicePropertyRequest = new SetDevicePropertyRequest();
    // 如果使用实例,此处传入真实实例id;如果公共实例,不需要设置。
    //createProductRequest.setIotInstanceId("iothub-test-xxx");
    setDevicePropertyRequest.setProductKey(pk);
    setDevicePropertyRequest.setDeviceName(dn);
    
    Map<String, Integer> properties = new HashMap<>();
    // key为物模型中属性标识符"acOutMeterIty",value需要遵循属性值规范:int类型,取值范围在0~200之间;
    properties.put("acOutMeterIty", 98);
    setDevicePropertyRequest.setItems(JSON.toJSONString(properties));
    
    SetDevicePropertyResponse response = null;
    try {
        response = defaultAcsClient.getAcsResponse(setDevicePropertyRequest);
    } catch (Exception e) {
        Log.error("执行失败:e:" + e.getMessage());
    }
    
    System.out.println("===============");
    System.out.println("setDeviceProperty request : " + JSON.toJSONString(setDevicePropertyRequest));
    System.out.println("setDeviceProperty response : " + JSON.toJSONString(response.getData()));
    System.out.println("setDeviceProperty requestId : " + response.getRequestId());
    System.out.println("===============");

    重点说明
    image.png
    下行控制如果为异步服务,需要通过订阅数据流转获取设备返回结果,订阅方式和数据结构详见“3.2 数据流转”章节介绍。

    关联介绍:“3.2.1 服务端订阅”中“重点说明”。

    3.2 数据流转

    平台提供两种数据流转方式:方式1)服务端订阅;方式2)规则引擎;

    3.2.1服务端订阅(P0)

    服务端订阅配置
    image.png
    “推送消息类型”选择“设备上报消息”,包括物模型属性上报、事件上报、设备下行指令结果(包括属性设置响应、服务控制响应)等消息。
    消息格式详见文档:https://help.aliyun.com/document_detail/73736.html

    test

    服务端订阅DEMO
    接入说明:https://help.aliyun.com/document_detail/143601.html

    /**
     * AMQP服务端订阅
    */
    //参数说明,请参见AMQP客户端接入说明文档。
    String accessKey = "***";
    String accessSecret = "***";
    String consumerGroupId = "***";
    //iotInstanceId:购买的实例请填写实例ID,公共实例请填空字符串""。
    String iotInstanceId = "";
    long timeStamp = System.currentTimeMillis();
    //签名方法:支持hmacmd5、hmacsha1和hmacsha256。
    String signMethod = "hmacsha1";
    //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
    //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
    String clientId = "TESTClientID";
    
    //userName组装方法,请参见AMQP客户端接入说明文档。
    String userName = clientId + "|authMode=aksign"
        + ",signMethod=" + signMethod
                + ",timestamp=" + timeStamp
                + ",authId=" + accessKey
                + ",iotInstanceId=" + iotInstanceId
                + ",consumerGroupId=" + consumerGroupId
                + "|";
    //计算签名,password组装方法,请参见AMQP客户端接入说明文档。
    String signContent = "authId=" + accessKey + "&timestamp=" + timeStamp;
    String password = doSign(signContent,accessSecret, signMethod);
    //接入域名,请参见AMQP客户端接入说明文档。
    String connectionUrl = "amqps://${uid}.iot-amqp.${regionId}.aliyuncs.com:5671?amqp.idleTimeout=80000";
    
    Hashtable<String, String> hashtable = new Hashtable<>();
    hashtable.put("connectionfactory.SBCF",connectionUrl);
    hashtable.put("queue.QUEUE", "default");
    hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
    Context context = new InitialContext(hashtable);
    ConnectionFactory cf = (ConnectionFactory)context.lookup("SBCF");
    Destination queue = (Destination)context.lookup("QUEUE");
    // Create Connection
    Connection connection = cf.createConnection(userName, password);
    ((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);
    // Create Session
    // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
    // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    connection.start();
    // Create Receiver Link
    MessageConsumer consumer = session.createConsumer(queue);
    consumer.setMessageListener(messageListener);
    }
    
    private static MessageListener messageListener = new MessageListener() {
        @Override
        public void onMessage(Message message) {
            try {
                //1.收到消息之后一定要ACK。
                // 推荐做法:创建Session选择Session.AUTO_ACKNOWLEDGE,这里会自动ACK。
                // 其他做法:创建Session选择Session.CLIENT_ACKNOWLEDGE,这里一定要调message.acknowledge()来ACK。
                // message.acknowledge();
                //2.建议异步处理收到的消息,确保onMessage函数里没有耗时逻辑。
                // 如果业务处理耗时过程过长阻塞住线程,可能会影响SDK收到消息后的正常回调。
                executorService.submit(() -> processMessage(message));
            } catch (Exception e) {
                logger.error("submit task occurs exception ", e);
            }
        }
    };
    
    /**
     * 在这里处理您收到消息后的具体业务逻辑。
    */
    private static void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String content = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            System.out.println("AMQP receive message"
                               + ", topic = " + topic
                               + ", messageId = " + messageId
                               + ", content = " + content);
        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }
    
    private static JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            logger.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }
    
        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            logger.error("onConnectionFailure, {}", error.getMessage());
        }
    
        /**
          * 连接中断。
          */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            logger.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }
    
        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            logger.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }
    
        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {}
    
        @Override
        public void onSessionClosed(Session session, Throwable cause) {}
    
        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {}
    
        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {}
    };
    
    /**
     * 计算签名,password组装方法,请参见AMQP客户端接入说明文档。
     */
    private static String doSign(String toSignString, String secret, String signMethod) throws Exception {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), signMethod);
        Mac mac = Mac.getInstance(signMethod);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(toSignString.getBytes());
        return Base64.encodeBase64String(rawHmac);
    }

    日志打印出订阅到的流转消息如下,符合预期。
    image.png

    重点说明
    下行控制如果为异步服务,需要通过订阅数据流转获取设备返回结果。订阅Topic为"/sys/{productKey}/{deviceName}/thing/downlink/reply/message",需要根据"requestId"关联请求和响应。

    关联介绍:“3.1 服务端调用API开发”中“重点说明”。

    image.png

    3.2.2 规则引擎数据订阅。

    配置SQL
    SQL介绍文档这里
    image.png

    调试SQL
    Payload数据格式文档这里
    image.png

    可以查看“调试结果”。

    test

    符合配置的SQL结果。

    转发数据
    可以转发到客户以下多种云产品中,本文选择AMQP作为示例验证。
    image.png

    image.png
    创建完成后,需要到规则列表页“启动”改规则。

    订阅数据
    服务端订阅代码可以复用上面“3.1”服务端订阅代码。差别就是服务端订阅,订阅的是Topic对应的完整Payload;而规则引擎流转AMQP,在消息流转过程可以对Payload做一些规则过滤或简单计算。
    以下日志精简报文是通过规则引擎过滤后获取的数据。
    image.png
    说明:同一组数据不要同时开通规则引擎和服务端订阅两种订阅模式,避免消息干扰。

    4 设备运行时

    设备量产之后,到达消费者手上,会开始激活上线进入到设备运行时。由于不属于开发态流程,本章节仅做简单介绍,目的是能让开发者知道开发态的配置在运行态如何产生作用,对设备接上阿里云IoT平台后的流程有个简单的认识。

    image.png

    本文通过物模型接入流程,介绍了平台设备连接、物模型规范校验、物模型数据、规则引擎、服务端订阅、开放API六大基础能力。
    设备全生命周期过程中,还有不少设备管理能力供客户选择,其中包括设备标签、设备分组、设备检索、OTA、设备运维、设备分发、文件上传、远程配置等,欢迎使用。

    4.1 连接

    设备连接过程,云端会对设备进行身份认证。

    4.2 物模型规范校验

    由于目前物模型配置仅提供强校验模式,物模型规范校验主要对设备上报的报文进行Alink协议解析、物模型数据规范校验。平台后续会陆续开放弱校验、免校验、数据去重能力。

    关联阅读:1.3 物模型配置

    4.3 设备管理能力

    4.3.1 设备标签
    介绍文档:https://help.aliyun.com/document_detail/73733.html
    4.3.2 设备分组
    介绍文档:https://help.aliyun.com/document_detail/90386.html
    4.3.3 OTA
    介绍文档:https://help.aliyun.com/document_detail/85700.html
    4.3.4 设备分发
    介绍文档:https://help.aliyun.com/document_detail/143450.html

    ]]>
    macOS 神器 Workflow ,让效率翻倍!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 滚动.gif

    背景

    一起回顾一个大家非常熟悉场景。

    上周开发一个需求,开发过程中,我需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些debug的信息。所以我登录到跳板机,此时我发现我忘了某一台机器的具体名字(通常能记得的人都是天才),所以需要用跳板机提供的 autoget 命令来通过服务器组名来获得机器的列表。

    此时我发现我连服务器组名都忘了,所以需要上 eagle eye 上查寻一下组名。这个查询可能得依靠我的记忆打出组名的前缀,通过 eagle eye 给出的补全提示列表来识别出其中那个我需要的组名。

    终于我得到了我的组名,为了避免我下次再忘记而不得不再繁琐的查询一遍,我选择把这些结果记录到我的记事本中,方便我下次查找使用。终于我登录了机器,可是不巧我忘了日志记录的位置,我想起某个同事曾经告诉过我日志的路径,于是我查找了与那个同事的聊天记录,找到了这个日志路径。同样为了防止忘记,我又把它记录到了我的记事本中方便下次查找。然后我把日志路径复制粘贴到命令行,我终于可以开始工作了。

    昨天我又开发了一个需求,我还需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些 debug 的信息。所以我登录到跳板机,需要用跳板机提供的 autoget 命令来通过服务器组名来获得机器的列表,我想起了我在记事本中记录了这个内容,于是我打开记事本,搜索了 keyword,在简单的翻找下,我找到了我想要的命令。

    进入到机器后,我发现日志的路径我还是没有记住。于是,我再次打开记事本,搜索了另一个 keyword ,再次翻找了一下,找到路径后把它复制粘贴到命令行,我一边粘贴一边想,我要是像在本机的终端环境中,把这些命令写成 bashrc 中的 alias 就好了。我又可以开始工作了。

    于是,我的记事本中的内容一般都是这样:

    image.png

    从上述两次流程的对比中,我们发现记事本已经给我们的工作带来很大的提效了。但是在昨天流程最后我的思考中,我们不难发现,这个提效,还有提升的空间。我认为任何用过 shell 的 alias 的同学都会认同我的观点:如果上述流程能用alias来记录这些冗长的命令,我们就不用麻烦记事本了不是吗?

    可是跳板机是公共资源,有严格的使用规范。具体服务器又是容器化部署,每次部署都会是一个新的容器,所以在当前的bashrc上写下什么并没有用。或许我们能寻找一个新的途径来实现这个需求。

    问题

    让我们从背景中总结我们正在面临的是哪些问题:

    1、工作中存在非常多冗长难记的信息,需要我们在各个场景反复输入。
    2、这些冗长的信息来源分散,查找起来非常麻烦耗时。
    每次需要输入时等要通过额外的操作,频繁切换聚焦的窗口来获取这些信息。
    3、这些问题虽然各自都占用了我们为数不多的时间和精力,但因为场景小而频繁,当乘以次数后,这些消耗也变得非常可观,并且非常影响我们的工作体验。

    思考

    让我们思考一下当我们想要使用 alias 的时候我们实际想要的是什么?我以个人的经验来总结,大概是以下几点:

    1、用一个很短的短词来替代一个需要高频输入的很长的句子。

    2、用一个更好记的词替代一个难记易忘的句。

    3、配置的成本可控,使用的成本很低。

    我们品一品1、2两点,其本质就是一个字典,由短语为key,长句为value。这个是我们程序员的好朋友了,我们可以简单的通过一个文件就能实现这份配置。事实上,我们使用记事本记录,其实本质也是在使用字典的特性。而第三点,让我瞬间想到使用 Alfred 的 Workflow 这个Mac上的神器。关于 Alfred 此处不做介绍,不知道或者想要了解的同学可以移步官网

    设计

    其实针对我们已经给出的需求,我们非常容易就可以得出一个设计思路。我们可以固定一个文件路径保存一个文件,这个文件以一种简单的格式或方式保存一个字典。编写一个 alfred 的 workflow 来解析这个文件形成一个 Map ,并通过搜索和匹配 key 来快速的获取 value ,而获取 value 最有效的方式就是把 value 输出到系统的剪切板中。

    考虑到 Mac 和 Alfred 的使用用户并不全是工程师,我们选择记录字典的格式最好越简单越好。所以我计划以普通的文本格式,每一行为一个键值对,第一个空格前的短词为 key ,第一个空格后的内容为 value 。直接让用户新建指定路径的文件并通过编辑文件的方式来管理的形式确实可以被一部分用户所接受,但是为了能面对更多用户,我认为以 workflow 的方式在增删字典的内容也同时是需要支持的。这样不想关心具体实现、不愿接触文本文件的用户同样可以无感使用。

    最后我给这个 workflow 取名为 EasyAlias。

    实现

    来看一下 workflow 的排版:

    image.png

    通过三个关键字的Alfred命令,分别实现设置alias(sal, set alias),删除alias(dal, delete alias),查找(gal, get alias)。

    其中sal和dal使用简单的keyword输入,而gal为了使用Alfred通过的展示候选列表和搜索匹配的能力,而使用了Script Filter作为输入。三者都通过shell调用了一个实现主要功能的python脚本easy_alias.py,通过传入不同的action参数来区分行为。

    sal:

    python easy_alias.py set {query}

    dal:

    python easy_alias.py del {query}

    gal:

    python easy_alias.py show {query}
    cat filter.output

    easy_alias.py

    # coding=utf8
    import sys
    import json
    from os import listdir, makedirs
    from os.path import isfile, join, exists, expanduser
    
    base_path = expanduser("~/.easy_alias")
    file_name = "alias_conf"
    file_path = join(base_path, file_name)
    
    alias_map = dict()
    
    def init():
        if not exists(base_path):
            makedirs(base_path)
        if not exists(file_path):
            open(file_path, 'w').close()
    
    def get_key_and_value(text):
        seqs = text.strip().split(' ')
        if len(seqs) < 2:
            return None, None
        key = seqs[0];
        value = reduce(lambda x, y: x.strip() + ' ' + y.strip(), seqs[1:])
        return key, value
    
    def get_alias_map():
        with open(file_path, 'r') as f:
            for line in f.readlines():
                k, v = get_key_and_value(line)
                if k == None or v == None:
                    continue
                alias_map[k] = v
    
    def set_alias():
        if len(sys.argv) < 3:
            return 
        text = sys.argv[2].strip()
        k, v = get_key_and_value(text)
        if k == None or v == None:
            return
        alias_map[k] = v
    
    def del_alias():
        if len(sys.argv) < 3:
            return 
        key = sys.argv[2].strip()
        new_content = ""
        if key in alias_map:
            alias_map.pop(key)
    
    def show_alias():
        items = list()
        for k, v in alias_map.iteritems():
            d = {
                "uid": k,
                "type": "default",
                "title": k,
                "subtitle": v,
                "arg": v,
                "autocomplete": k,
                "icon": {
                    "type": "fileticon",
                    "path": "icon.png"
                }
            }
            items.append(d)
        show = {"items": items}
        with open('filter.output', 'w') as f:
            f.write(json.dumps(show))
    
    def write_map_to_file():
        file_content = ''
        for k, v in alias_map.iteritems():
            file_content += k + ' ' + v + 'n'
        with open(file_path, 'w') as f:
            f.write(file_content)
    
    if __name__ == '__main__':
        init()
        get_alias_map()
        action = sys.argv[1]
    
        with open(join(base_path, 'logs'), 'a') as f:
            f.write(str(sys.argv) + 'n')
    
        if (action == 'set'):
            set_alias()
        if (action == 'del'):
            del_alias()
        if (action == 'show'):
            show_alias()
    
        write_map_to_file()

    效果

    设置一个alias

    image.png

    查找一个alias

    image.png

    删除一个alias

    image.png

    如果觉得通过sal设置和dal删除的方式太麻烦,也可以直接编辑~/.easy_alias/alias_conf

    image.png

    保存文件再查询

    image.png

    作业

    这个 workflow 本身很简单很好实现。本文也希望不仅仅只是一个简单分享,希望能与读者有所互动,所以打算留个回家作业。

    可以发现现在 dal 命令现在需要使用者盲打key,而不是像gal这样可以搜索补全。这会给使用者带来一定烦恼。回家作业就是将 dal 命令也改造成像 gal 一样可以搜索补全的形式。

    作业下载
    EasyAliasPro

    总结

    让我们回过头看一下我们再开始时面对的问题是否得到了很好的解决。

    我们无法改变工作中频繁需要冗长信息的状况,但是我们通过访问剪切板的方式让输入变得简单。
    我们用一个文件将这些信息集中在一起,并且通过工具打打提升了我们检索这些信息的效率。
    Alfred提供给我们一个在检索并获取这些信息上无需切换窗口,并且操作非常简单的方式。

    很高兴,我们很大程度上解决了我们先前提出的这些问题!

    最后

    今天我又开发了一个需求,我还需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些debug的信息。所以我登录到跳板机,唤醒了Alfred,输入gal ahostp,并将结果粘贴在命令行获得了机器列表。登上机器后,我再次唤醒Alfred,输入gal alog,并粘贴在命令行中。现在我可以开始工作了。

    关注「淘系技术」微信公众号,一个有温度有内容的技术社区~

    image.png

    ]]>
    测试面试题集锦(三)| 计算机网络和数据库篇(附答案)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 软件测试工程师面试题系列篇 | 目录
    1. 测试常见问题与流程篇
    2. 测试工具篇
    3. 计算机网络知识与数据库篇
    4. Linux 篇
    5. Python 编程篇
    6. 自动化测试篇:包含 Selenium、Appium 和接口测试
    7. 性能测试篇
    8. 软素质篇:10 大灵魂拷问
    9. 反问面试官篇
    10. 计算机网络篇(基础知识)
    1.擅长哪些开发语言?
    • 学习过 Java,C 等
    • 半精通 Python
    2.输入 URL 到网页显示出来的全过程
    1. 输入网址
    2. DNS解析
    3. 建立tcp连接
    4. 客户端发送HTTP请求
    5. 服务器处理请求
    6. 服务器响应请求
    7. 浏览器展示HTML
    8. 浏览器发送请求获取其他在HTML中的资源。
    3.HTTP 和 HTTPS 的区别
    • HTTPS 里面是要有证书的,HTTP 并没有证书。证书的作用是证明你是这个网站的拥有者。谁去证明?最顶级的 CA 去帮你证明,这些顶级的 CA 都是浏览器、操作系统本身就自动帮你集成,而且自动添加到设置信任里面去;
    • HTTPS 要兼顾安全+性能的方面,由于对称式加密虽然速度很快,但是安全性特别的低,因为双方要规定对称式加密的秘钥,别人都无法知道,但你怎么能确保别人不知道你的秘钥呢,因此需要有非对称式加密去保证安全,但非对称式加密速度又很慢,如果客户端和服务器端都用非对称式加密,网络得卡死了。所以当双方建立好了非对称加密后,再约定一个随机数,等大家都非对称解密了之后呢,就拿到只有对方知道的唯一随机数(秘钥),就可以用秘钥来进行对称式加密和解密了;
    4.HTTP 的报文结构
    • HTTP请求报文:一个HTTP请求报文由请求行、请求头部、空行和请求数据4个部分组成
    • HTTP响应报文:HTTP响应也由三个部分组成,分别是:状态行、消息报头、响应正文
    5.HTTP 常见的响应状态码
    • 200 请求已成功,请求所希望的响应头或数据体将随此响应返回。
    • 201 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 - - URI 已经随 Location 头信息返回
    • 202 服务器已接受请求,但尚未处理
    • 301 (永久移动) 请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
    • 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
    • 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
    • 304 (未修改) 自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
    • 305 (使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。
    • 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
    • 401 当前请求需要用户验证。如果当前请求已经包含了 Authorization 证书,那么
    • 401 响应代表着服务器验证已经拒绝了那些证书
    • 403 服务器已经理解请求,但是拒绝执行它。与 401 响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交
    • 404 请求失败,请求所希望得到的资源未被在服务器上发现
    • 500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。
    • 501 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
    • 502 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
    • 503 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
    6.cookie 和 session 机制的区别
    • cookies 数据保存在客户端,session 数据保存在服务器端;
    • cookies 可以减轻服务器压力,但是不安全,容易进行 cookies 欺骗;
    • session 较安全,但占用服务器资源
    7.TCP 和 UDP 的区别
    • TCP:面向连接,可靠的,速度慢,效率低
    • UDP:无连接、不可靠、速度快、效率高
    8.TCP 为什么是三次握手和四次挥手
    • 三次握手能保证数据可靠传输又能提高传输效率。若握手是两次:如果只是两次握手, 至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认;
    • 要保证双方都关闭了连接。因为 TCP 是全双工的,就是要等到两边都发送 fin 包确认双方都没有数据传输后才关闭;
    9.TCP为什么最后挥手后会有time_wait
    • 为了保证可靠的断开TCP的双向连接,确保足够的时间让对方收到 ACK 包。若客户端回复的 ACK 丢失,server 会在超时时间到来时,重传最后一个 fin 包,处于 TIME_WAIT 状态的 client 可以继续回复 Fin 包,发送 ACK。
    • 保证让迟来的 TCP 报文段有足够的时间被识别和丢弃,避免新旧连接混淆。有些路由器会缓存没有收到的数据包,如果新的连接开启,这些数据包可能就会和新的连接中的数据包混在一起。连接结束了,网络中的延迟报文也应该被丢弃掉,以免影响立刻建立的新连接。
    10.简要说明 HTTP 请求中的 Post 和 Get 有哪些区别的地方
    • 请求头多了 content-length 和 content-type 字段
    • Post 可以附加 body,可以支持 form、json、xml、binary 等各种数据格式
    • 行业通用规范
    • 无状态变化的建议使用 Get
    • 数据的写入与状态的修改建议使用 Post
    • 基于 HTTP 协议:都是请求返回数据,Get 将请求体放在头上,只发一次请求,Post 将请求体放在内部,需要发送两次请求
    • GET 在浏览器回退时是无害的,而 POST 会再次提交请求。
    • GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。
    • GET 请求只能进行 URL 编码,而 POST 支持多种编码方式。
    • GET 请求在 URL 中传送的参数是有长度限制的,而 POST 么有。
    • 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。
    • GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。
    11.如果一个请求,返回的状态码是 200,但是没有内容,可能发生了什么?
    • 请求头缺失或错误
    • 参数 length 不符
    • 以上为个人理解,有误请指正。

    数据库篇

    1. 工作中常使用的 SQL 语法有哪些?
    • create table、create view、 select from where、insert into、update set values、delete、alter、order by、having
    2.数据库存储过程
    • 一组数据库操作命令,当作是自己写的一个方法,一系列步骤自己去封装(个人理解)
    3.SQL 常见查询语句编写(此处仅举例常见的查询语句,如有更多坑,希望补充)
    a.查询所有学生的数学成绩,显示学生姓名 name, 分数, 由高到低。
    SELECT a.name, b.score FROM student a, grade b WHERE a.id = b.id AND kemu = '数学' ORDER BY score DESC;
    b.统计每个学生的总成绩(由于学生可能有重复名字),显示字段:学生 id,姓名,总成绩。
    SELECT a.id, a.name, c.sum_score from student a, (SELECT b.id, sum(b.score) as sum_score FROM grade b GROUP BY id) c WHERE a.id = c.id ORDER BY sum_score DESC;
    c.列出各门课程成绩最好的学生, 要求显示字段: 学号,姓名,科目,成绩
    SELECT c.id , a.name, c.kemu, c.score FROM grade c, student a,(SELECT b.kemu, MAX(b.score) as max_score FROM grade b GROUP BY kemu) t WHERE c.kemu = t.kemu AND c.score = t.max_score AND a.id = c.id
    4.慢查询是什么意思?
    • 开启慢查询日志,可以让 MySQL 记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。
    5.导致数据库性能差的可能原因有哪些?
    • 硬件环境问题,如磁盘IO
    • 查询语句问题,如join、子查询、没建索引
    • 索引失效,建了索引,查询的时候没用上
    • 查询关联了太多的join
    • 服务器关联缓存,线程数等
    • 表中存在冗余字段,在生成笛卡尔积时耗费多余的时间
    6.Redis 缓存应用场景
    • 需要将数据缓存在内存中,提升查询效率
    • 这里希望大家补充
    7.怎么定位 Redis 缓存失效问题(缓存坏了)
    • Redis 的知识,了解的不是很多
    • 抛砖引玉,请大家指正和补充。

    更多内容,我们在后续文章分享。

    免费领取:接口测试+性能测试+自动化测试+测试开发+测试用例+简历模板+测试文档

    ]]>
    详细讲解!RabbitMQ防止数据丢失-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 思维导图

    在这里插入图片描述

    一、分析数据丢失的原因

    分析RabbitMQ消息丢失的情况,不妨先看看一条消息从生产者发送到消费者消费的过程:

    可以看出,一条消息整个过程要经历两次的网络传输:从生产者发送到RabbitMQ服务器,从RabbitMQ服务器发送到消费者

    在消费者未消费前存储在队列(Queue)中

    所以可以知道,有三个场景下是会发生消息丢失的:

    • 存储在队列中,如果队列没有对消息持久化,RabbitMQ服务器宕机重启会丢失数据。
    • 生产者发送消息到RabbitMQ服务器过程中,RabbitMQ服务器如果宕机停止服务,消息会丢失。
    • 消费者从RabbitMQ服务器获取队列中存储的数据消费,但是消费者程序出错或者宕机而没有正确消费,导致数据丢失。

    针对以上三种场景,RabbitMQ提供了三种解决的方式,分别是消息持久化,confirm机制,ACK事务机制。

    二、消息持久化

    RabbitMQ是支持消息持久化的,消息持久化需要设置:Exchange为持久化和Queue持久化,这样当消息发送到RabbitMQ服务器时,消息就会持久化。

    首先看Exchange交换机的类图:

    看这个类图其实是要说明上一篇文章介绍的四种交换机都是AbstractExchange抽象类的子类,所以根据java的特性,创建子类的实例会先调用父类的构造器,父类也就是AbstractExchange的构造器是怎么样的呢?

    从上面的注释可以看到durable参数表示是否持久化。默认是持久化(true)。创建持久化的Exchange可以这样写:

        @Bean
        public DirectExchange rabbitmqDemoDirectExchange() {
            //Direct交换机
            return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
        }

    接着是Queue队列,我们先看看Queue的构造器是怎么样的:

    也是通过durable参数设置是否持久化,默认是true。所以创建时可以不指定:

        @Bean
        public Queue fanoutExchangeQueueA() {
            //只需要指定名称,默认是持久化的
            return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A);
        }

    这就完成了消息持久化的设置,接下来启动项目,发送几条消息,我们可以看到:


    怎么证明是已经持久化了呢,实际上可以找到对应的文件:
    在这里插入图片描述
    找到对应磁盘中的目录:

    消息持久化可以防止消息在RabbitMQ Server中不会因为宕机重启而丢失

    三、消息确认机制

    3.1 confirm机制

    在生产者发送到RabbitMQ Server时有可能因为网络问题导致投递失败,从而丢失数据。我们可以使用confirm模式防止数据丢失。工作流程是怎么样的呢,看以下图解:
    在这里插入图片描述
    从上图中可以看到是通过两个回调函数confirm()、returnedMessage()进行通知。

    一条消息从生产者发送到RabbitMQ,首先会发送到Exchange,对应回调函数confirm()。第二步从Exchange路由分配到Queue中,对应回调函数则是returnedMessage()

    代码怎么实现呢,请看演示:

    首先在application.yml配置文件中加上如下配置:

    spring:
      rabbitmq:
        publisher-confirms: true
    #    publisher-returns: true
        template:
          mandatory: true
    # publisher-confirms:设置为true时。当消息投递到Exchange后,会回调confirm()方法进行通知生产者
    # publisher-returns:设置为true时。当消息匹配到Queue并且失败时,会通过回调returnedMessage()方法返回消息
    # spring.rabbitmq.template.mandatory: 设置为true时。指定消息在没有被队列接收时会通过回调returnedMessage()方法退回。

    有个小细节,publisher-returns和mandatory如果都设置的话,优先级是以mandatory优先。可以看源码:
    在这里插入图片描述
    接着我们需要定义回调方法:

    @Component
    public class RabbitmqConfirmCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
        private Logger logger = LoggerFactory.getLogger(RabbitmqConfirmCallback.class);
    
        /**
         * 监听消息是否到达Exchange
         *
         * @param correlationData 包含消息的唯一标识的对象
         * @param ack             true 标识 ack,false 标识 nack
         * @param cause           nack 投递失败的原因
         */
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (ack) {
                logger.info("消息投递成功~消息Id:{}", correlationData.getId());
            } else {
                logger.error("消息投递失败,Id:{},错误提示:{}", correlationData.getId(), cause);
            }
        }
    
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            logger.info("消息没有路由到队列,获得返回的消息");
            Map map = byteToObject(message.getBody(), Map.class);
            logger.info("message body: {}", map == null ? "" : map.toString());
            logger.info("replyCode: {}", replyCode);
            logger.info("replyText: {}", replyText);
            logger.info("exchange: {}", exchange);
            logger.info("routingKey: {}", exchange);
            logger.info("------------> end <------------");
        }
    
        @SuppressWarnings("unchecked")
        private <T> T byteToObject(byte[] bytes, Class<T> clazz) {
            T t;
            try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                 ObjectInputStream ois = new ObjectInputStream(bis)) {
                t = (T) ois.readObject();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
            return t;
        }
    }

    我这里就简单地打印回调方法返回的消息,在实际项目中,可以把返回的消息存储到日志表中,使用定时任务进行进一步的处理。

    我这里是使用RabbitTemplate进行发送,所以在Service层的RabbitTemplate需要设置一下:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        @Resource
        private RabbitmqConfirmCallback rabbitmqConfirmCallback;
    
        @Resource
        private RabbitTemplate rabbitTemplate;
    
        @PostConstruct
        public void init() {
            //指定 ConfirmCallback
            rabbitTemplate.setConfirmCallback(rabbitmqConfirmCallback);
            //指定 ReturnCallback
            rabbitTemplate.setReturnCallback(rabbitmqConfirmCallback);
        }
        
        @Override
        public String sendMsg(String msg) throws Exception {
            Map<String, Object> message = getMessage(msg);
            try {
                CorrelationData correlationData = (CorrelationData) message.remove("correlationData");
                rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, message, correlationData);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
        
        private Map<String, Object> getMessage(String msg) {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            CorrelationData correlationData = new CorrelationData(msgId);
            String sendTime = sdf.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            map.put("correlationData", correlationData);
            return map;
        }
    }

    大功告成!接下来我们进行测试,发送一条消息,我们可以控制台:
    在这里插入图片描述
    假设发送一条信息没有路由匹配到队列,可以看到如下信息:
    在这里插入图片描述
    这就是confirm模式。它的作用是为了保障生产者投递消息到RabbitMQ不会出现消息丢失

    3.2 事务机制(ACK)

    最开始的那张图已经讲过,消费者从队列中获取到消息后,会直接确认签收,假设消费者宕机或者程序出现异常,数据没有正常消费,这种情况就会出现数据丢失

    所以关键在于把自动签收改成手动签收,正常消费则返回确认签收,如果出现异常,则返回拒绝签收重回队列。
    在这里插入图片描述
    代码怎么实现呢,请看演示:

    首先在消费者的application.yml文件中设置事务提交为manual手动模式:

    spring:
      rabbitmq:
        listener:
          simple:
            acknowledge-mode: manual # 手动ack模式
            concurrency: 1 # 最少消费者数量
            max-concurrency: 10 # 最大消费者数量

    然后编写消费者的监听器:

    @Component
    public class RabbitDemoConsumer {
    
        enum Action {
            //处理成功
            SUCCESS,
            //可以重试的错误,消息重回队列
            RETRY,
            //无需重试的错误,拒绝消息,并从队列中删除
            REJECT
        }
    
        @RabbitHandler
        @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))
        public void process(String msg, Message message, Channel channel) {
            long tag = message.getMessageProperties().getDeliveryTag();
            Action action = Action.SUCCESS;
            try {
                System.out.println("消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:" + msg);
                if ("bad".equals(msg)) {
                    throw new IllegalArgumentException("测试:抛出可重回队列的异常");
                }
                if ("error".equals(msg)) {
                    throw new Exception("测试:抛出无需重回队列的异常");
                }
            } catch (IllegalArgumentException e1) {
                e1.printStackTrace();
                //根据异常的类型判断,设置action是可重试的,还是无需重试的
                action = Action.RETRY;
            } catch (Exception e2) {
                //打印异常
                e2.printStackTrace();
                //根据异常的类型判断,设置action是可重试的,还是无需重试的
                action = Action.REJECT;
            } finally {
                try {
                    if (action == Action.SUCCESS) {
                        //multiple 表示是否批量处理。true表示批量ack处理小于tag的所有消息。false则处理当前消息
                        channel.basicAck(tag, false);
                    } else if (action == Action.RETRY) {
                        //Nack,拒绝策略,消息重回队列
                        channel.basicNack(tag, false, true);
                    } else {
                        //Nack,拒绝策略,并且从队列中删除
                        channel.basicNack(tag, false, false);
                    }
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    解释一下上面的代码,如果没有异常,则手动确认回复RabbitMQ服务端basicAck(消费成功)。

    如果抛出某些可以重回队列的异常,我们就回复basicNack并且设置重回队列。

    如果是抛出不可重回队列的异常,就回复basicNack并且设置从RabbitMQ的队列中删除。

    接下来进行测试,发送一条普通的消息"hello":
    在这里插入图片描述
    解释一下ack返回的三个方法的意思。

    ①成功确认

    void basicAck(long deliveryTag, boolean multiple) throws IOException;

    消费者成功处理后调用此方法对消息进行确认。

    • deliveryTag:该消息的index
    • multiple:是否批量.。true:将一次性ack所有小于deliveryTag的消息。

    ②失败确认

    void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
    • deliveryTag:该消息的index。
    • multiple:是否批量。true:将一次性拒绝所有小于deliveryTag的消息。
    • requeue:被拒绝的是否重新入队列。

    ③失败确认

    void basicReject(long deliveryTag, boolean requeue) throws IOException;
    • deliveryTag:该消息的index。
    • requeue:被拒绝的是否重新入队列。

    basicNack()和basicReject()的区别在于:basicNack()可以批量拒绝,basicReject()一次只能拒接一条消息

    四、遇到的坑

    4.1 启用nack机制后,导致的死循环

    上面的代码我故意写了一个bug。测试发送一条"bad",然后会抛出重回队列的异常。这就有个问题:重回队列后消费者又消费,消费抛出异常又重回队列,就造成了死循环。
    在这里插入图片描述
    那怎么避免这种情况呢?

    既然nack会造成死循环的话,我提供的一个思路是不使用basicNack(),把抛出异常的消息落库到一张表中,记录抛出的异常,消息体,消息Id。通过定时任务去处理

    如果你有什么好的解决方案,也可以留言讨论~

    4.2 double ack

    有的时候比较粗心,不小心开启了自动Ack模式,又手动回复了Ack。那就会报这个错误:

    消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:java技术爱好者
    2020-08-02 22:52:42.148 ERROR 4880 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)
    2020-08-02 22:52:43.102  INFO 4880 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer@f4a3a8d: tags=[{amq.ctag-8MJeQ7el_PNbVJxGOOw7Rw=rabbitmq.demo.topic}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,5), conn: Proxy@782a1679 Shared Rabbit Connection: SimpleConnection@67c5b175 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 56938], acknowledgeMode=AUTO local queue size=0

    出现这个错误,可以检查一下yml文件是否添加了以下配置:

    spring:
      rabbitmq:
        listener:
          simple:
            acknowledge-mode: manual
            concurrency: 1
            max-concurrency: 10

    如果上面这个配置已经添加了,还是报错,有可能你使用@Configuration配置了SimpleRabbitListenerContainerFactory,根据SpringBoot的特性,代码优于配置,代码的配置覆盖了yml的配置,并且忘记设置手动manual模式

    @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory);
            //设置手动ack模式
            factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            return factory;
        }

    如果你还是有报错,那可能是写错地方了,写在生产者的项目了。以上的配置应该配置在消费者的项目。因为ack模式是针对消费者而言的。我就是写错了,写在生产者,折腾了几个小时,泪目~

    4.3 性能问题

    其实手动ACK相对于自动ACK肯定是会慢很多,我在网上查了一些资料,性能相差大概有10倍。所以一般在实际应用中不太建议开手动ACK模式。不过也不是绝对不可以开,具体情况具体分析,看并发量,还有数据的重要性等等。

    所以在实际项目中还需要权衡一下并发量和数据的重要性,再决定具体的方案

    4.4 启用手动ack模式,如果没有及时回复,会造成队列异常

    如果开启了手动ACK模式,但是由于代码有bug的原因,没有回复RabbitMQ服务端,那么这条消息就会放到Unacked状态的消息堆里,只有等到消费者的连接断开才会转到Ready消息。如果消费者一直没有断开连接,那Unacked的消息就会越来越多,占用内存就越来越大,最后就会出现异常。

    这个问题,我没法用我的电脑演示,我的电脑太卡了。

    五、总结

    通过上面的学习后,总结了RabbitMQ防止数据丢失有三种方式:

    • 消息持久化
    • 生产者消息确认机制(confirm模式)
    • 消费者消息确认模式(ack模式)

    上面所有例子的代码都上传github了:

    https://github.com/yehongzhi/mall

    如果你觉得这篇文章对你有用,点个赞吧~

    你的点赞是我创作的最大动力~

    想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!
    在这里插入图片描述

    能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

    ]]>
    kafka_架构模型-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Kafka架构模型

    p1014.png

    Kafka消费速度快:

    1. 页缓存:找个磁盘当内存;
    2. kafka采用顺序读写,比固态磁盘快

    p1015.png

    1. 如果消费速度太慢,更改topic的分区个数,就会有很多线程来消费。

    flume与kafka的整合

    flume监控文件夹,有新文件就搜集起来到kafka队列中

    1. source:spoolDir Source
    2. channel:memory channel
    3. sink:数据到kafka里面

    副本默认2

    注:仅作笔记。

    ]]>
    智能工作:人工智能将如何重塑远程工作-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    --------点击屏幕右侧或者屏幕底部“+订阅”,关注我,随时分享机器智能最新行业动态及技术干货----------

    image.png

    尽管人工智能的出现受到了雇主和雇员的不同程度的怀疑和恐惧,但在远程工作中,它的潜力不可低估。

    机器学习已经在积极地为世界各地的求职者增加就业机会,而 AI 可能很快会完全消除重复性工作——从而使企业有更多时间培训工人承担更多技能工作。

    远程工作的吸引力在全球范围内呈指数级增长。尽管企业在接受远程工作方面有些迟缓,但 COVID-19 大流行无意中展示了技术如何让员工能够轻松地以类似的生产率水平工作——尽管他们不在工作场所。

    随着 COVID-19 迫使越来越多的员工在自己家里设立办公室,员工的办公室生活会不会再次发生变化?人工智能和机器学习真的能为更多的公司维持在家工作(WFH)文化吗?

    使用AI管理远程工作

    人力资源经理和员工必须执行许多任务,以使其符合雇用的法律要求以及其各自公司发布的政策。当考虑到所有这些合规性时,找到合适的候选人可能是一个耗时的过程。但是,企业可以创建远程职位,以减轻经理或内部员工的负担。

    对 WFH 的批评之一是企业监视外部工人的生产力和质量的能力。幸运的是,人工智能和机器学习可以帮助您。团队负责人、主管和经理都可以求助于机器学习程序,以一种非侵入性和准确的方式监控员工的表现。

    更现代的系统能够通过调查工具利用信息,提供公正的绩效评估,并根据具体情况提供准确的报告,指出员工各自的优势和劣势。

    在这里,技术起了带头作用,并创建了难以通过人工管理进行复制的分析水平。对于在远程位置工作的拥有大量员工的公司而言尤其如此。

    未来的匹配

    人力资源专业人士和招聘经理都会发现寻找合适人选的过程非常耗时。招聘往往是乏味的,当有很多申请者时,总有一个危险,即有技能的候选人可能会被忽视。

    人工智能将很快成为内部招聘的关键部分。能够自动筛选数百个简历,在线个人资料和求职信的程序将能够挑选出候选人的关键属性和理想素质,并在适当时突出显示他们。

    技术也将有助于填补远程职位。考虑到这一点,AI 和 WFH 确实是未来的匹配。远程工作将帮助企业减少内部支出,并允许熟练的员工根据公司的属性为其工作,而不是根据其与办公室的距离来为他们工作。

    匹配AI和远程工作可帮助招聘人员甄选最合适的候选人,并吸引希望在家中工作的潜在员工。

    克服远程工作的弊端

    继毁灭性的 COVID-19 大流行之后,不可避免的是,越来越多的企业将以更快的速度接受 WFH。但是,虽然对于更多员工来说,远程工作已成为人们的首选,但并不是所有人都理想的选择。

    一些员工可能由于有限的共享生活空间而无法在家中成功工作,因此会发现很难提高工作效率。在其他地方,远程工作对留守父母和照料者都有很大的好处。

    COVID-19 周围的气候使远程工作成为帮助保护员工的基本选择。反过来,这正帮助公司更多地了解 WFH 和 AI 的潜力。越来越多的企业开始制定可持续的 WFH 计划,以帮助管理生产力并从远方有效地委派任务。

    这些新方法对于为员工开发更多远程机会可能具有重要意义——为全职远程工作打开了大门。

    自适应人工智能

    当然,AI 带有警告。如今,人工智能不可避免地会承担一些人类工人所从事的工作。但是,它为创建更多就业机会打开了大门。

    过去曾从事 WFH 任务的人会意识到AI技术的强大功能。在继续开发人工智能系统时,至关重要的是,工人必须掌握控制此类系统所需的技能和知识。

    COVID-19 的不幸到来为全球员工提供了远程工作路测。尽管存在一些警告和局限性,但人工智能将很快改变我们对 WFH 职位的看法,并为希望雇用和与来自世界各地的人才合作的企业开辟新天地。

    image.png

    原文链接:https://ai.51cto.com/art/202008/623196.htm
    文章转自51cto,本文一切观点和《机器智能技术》圈子无关

    ]]>
    Centos7系统安装远程桌面服务-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Xrdp是Microsoft远程桌面协议(RDP)的一个开源实现,它允许以图形方式控制远程系统。使用RDP,您可以登录到远程计算机并创建一个真正的桌面会话,就像您登录到本地计算机一样。
    系统环境
    服务端:Centos7.7 Minimal
    客户端:Windows10
    安装桌面环境
    本实验中安装的系统没有安装桌面环境,我们需要自己安装,如果已经安装桌面了清跳过这一步。Centos7提供了"Cinnamon Desktop","MATE Desktop","GNOME Desktop","KDE Plasma Workspaces","LXQt Desktop","Xfce"让我们安装。

    下面的命令列出可用环境组:

    [root@localhost ~]# yum grouplist
    Loaded plugins: fastestmirror
    There is no installed groups file.
    Maybe run: yum groups mark convert (see man yum)
    Loading mirror speeds from cached hostfile

    • base: mirrors.tuna.tsinghua.edu.cn
    • epel: mirrors.aliyun.com
    • extras: mirrors.aliyun.com
    • updates: mirrors.aliyun.com
      Available Environment Groups:

    Minimal Install
    Compute Node
    Infrastructure Server
    File and Print Server
    Cinnamon Desktop
    MATE Desktop
    Basic Web Server
    Virtualization Host
    Server with GUI
    GNOME Desktop
    KDE Plasma Workspaces
    Development and Creative Workstation
    Available Groups:
    Cinnamon
    Compatibility Libraries
    Console Internet Tools
    Development Tools
    Educational Software
    Electronic Lab
    Fedora Packager
    General Purpose Desktop
    Graphical Administration Tools
    Haskell
    LXQt Desktop
    Legacy UNIX Compatibility
    MATE
    Milkymist
    Scientific Support
    Security Tools
    Smart Card Support
    System Administration Tools
    System Management
    TurboGears application framework
    Xfce
    Done
    我们可以选择自己喜欢的桌面环境,在这里选择安装Xfce桌面:

    [root@localhost ~]# yum -y install epel-release && yum groupinstall Xfce
    安装Xrdp
    [root@localhost ~]# yum -y install xrdp
    安装完成之后,设置开机启动并启动xrdp

    [root@localhost ~]# systemctl start xrdp && systemctl enable xrdp
    创建~/.Xclients,设置默认启动xfce4桌面

    [root@localhost ~]# echo "xfce4-session" > ~/.Xclients
    [root@localhost ~]# chmod +x .Xclients
    在客户端远程连接
    Centos7安装Xrdp远程桌面服务Centos7安装Xrdp远程桌面服务

    Centos7安装Xrdp远程桌面服务Centos7安装Xrdp远程桌面服务

    Centos7安装Xrdp远程桌面服务Centos7安装Xrdp远程桌面服务

    总结
    安装Xrdp服务器允许您通过图形界面从本地管理CentOS 7服务器。

    本文原创地址:https://www.linuxprobe.com/centos7-xrdp-remote-desktop.html编辑:逄增宝,审核员:逄增宝

    ]]>
    全新出击!《Java开发手册(嵩山版)》解读手册升级下载-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 《Java开发手册(嵩山版)》解读版升级下载

    随着《Java开发手册(嵩山版)》的发布,解读再升级!灵魂13问随新版JAVA开发手册重磅回归,一线大厂怎么用JAVA?千万阅读量技术博主15问为你全面剖析。

    作者介绍
    Hollis,一个对Coding有着独特追求的人,现任阿里巴巴技术专家,个人技术博主,技术文章全网阅读量数千万,《程序员的三门课》联合作者。

    免费下载
    《〈Java开发手册(嵩山版)〉灵魂15问》

    或者复制该链接到浏览器完成下载或分享:https://developer.aliyun.com/topic/download?id=811

    java 灵魂15问.jpg

    精彩导读

    image.png

    一、为什么禁止使用Apache Beanutils进行属性的copy?
    市面上有很多类似的属性拷贝工具类,比较常用的有
    1、Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer
    那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢?
    image.png
    接下来就聚焦于对比这几个类库的性能问题来分析。>>点击查看详情

    二、为什么要求日期格式化时必须有使用y表示年,而不能用Y?
    在Java中进行日期处理大家一定都不陌生,我们经常会需要在代码中进行日期的转换、日期的格式化等操作。

    而一般我们进行日期格式化的时候都会使用SimpleDateFormat工具,之前我们有一篇文章介绍过SimpleDateFormat的线程安全问题,这一篇文章再来介绍一个和SimpleDateFormat有关,很容易被忽视,而一旦忽视可能导致大故障的问题。>>点击查看详情

    三、《 Java 开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事?
    手册中有一条规约引起了作者的关注,那就是手册中提到在三目运算符使用过程中,需要注意自动拆箱导致的NullPointerException(后文简称:NPE)问题:
    image.png
    具体是怎样的呢?>>点击查看详情

    四、为什么建议初始化HashMap的容量大小?
    我们之前提到过,《Java 开发手册》中建议我们设置 HashMap 的初始化容量。
    image.png
    那么,为什么要这么建议?>>点击查看详情

    五、Java开发手册建议创建HashMap时设置初始化容量, 但是多少合适呢?
    HashMap 有扩容机制,就是当达到扩容条件时会进行扩容。HashMap 的扩容条件就是当 HashMap 中的元素个数(size)超过临界值(threshold)时就会自动扩容。在 HashMap 中,threshold = loadFactor * capacity

    所以,如果我们没有设置初始容量大小,随着元素的不断增加,HashMap 会发生多次扩容,而 HashMap 中的扩容机制决定了每次扩容都需要重建 hash 表,是非常影响性能的。>>点击查看创建HashMap时设置初始化容量多少合适

    六、为什么禁止使用Executors创建线程池?
    为什么说可以通过Executors静态工厂构建线程池,但一般不建议这样使用。
    本节我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?>>点击查看详情

    七、为什么要求谨慎使用ArrayList中的subList方法?
    集合是Java开发日常开发中经常会使用到的。

    关于集合类,《Java开发手册》中其实还有另外一个规定:
    image.png
    本节就来分析一下为什么会有如此建议?其背后的原理是什么?>>点击查看详情

    八、为什么不建议在for循环中使用“+”进行字符串拼接?
    使用+拼接字符串,其实只是Java提供的一个语法糖,那么他的内部原理到底是如何实现的。>>点击查看详情

    语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

    九、为什么禁止在for each循环里进行元素的remove/add操作?
    在Java开发手册中,有这样一条规定:
    image.png
    本节就来深入分析一下该规定背后的思考。>>点击查看详情

    十、为什么禁止工程师直接使用日志系统(Log4j、Log back) 中的API?
    作为Java程序员,我想很多人都知道日志对于一个程序的重要性,尤其是Web应用。很多时候,日志可能是我们了解应用程序如何执行的唯一方式。

    所以,日志在Java Web应用中至关重要,但是,很多人却以为日志输出只是一件简单的事情,所以会经常忽略和日志相关的问题。>>点击查看详情

    十一、为什么禁止把SimpleDateFormat定义成static变量?
    在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。

    最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。本节就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。>>点击查看详情

    十二、为什么禁止开发人员使用is Success作为变量名?
    在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。

    关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本节就来围绕这个简单分析一下,到底该如何定一个布尔类型的成员变量。>>点击查看详情

    十三、为什么禁止开发人员修改serialVersionUID字段的值?
    关于serialVersionUID 。这个字段到底有什么用?如果不设置会怎么样?为什么《Java开发手册》中有以下规定:
    image.png
    本节带你一探究竟。>>点击查看详情

    十四、为什么建议开发者谨慎使用继承?
    对于很多开发者来说,继承肯定都是不陌生的。但是,继承一定适合所有的场景吗?毫无忌讳的使用继承来做代码扩展真的好吗?
    为什么《Java开发手册》中有一条规定:谨慎使用继承的方式进行扩展,优先使用组合的方式实现。>>点击查看详情

    十五、为什么禁止使用count(列名) 或count(常量) 来替代count(*)?
    除了COUNT(id)和COUNT(*)以外,还可以使用COUNT(常量)(如COUNT(1))来统计行数,那么这三条SQL语句有什么区别呢?到底哪种效率更高呢?为什么《Java开发手册》中强制要求不让使用 COUNT(列名)或 COUNT(常量)来替代 COUNT(*)呢?
    image.png
    本节就这些问题带来解答。>>点击查看详情


    藏经阁系列电子书

    阿里云开发者社区——藏经阁系列电子书,汇聚了一线大厂的技术沉淀精华,爆款不断。
    点击链接获取海量免费电子书:https://developer.aliyun.com/topic/ebook

    开发者藏经阁.jpg

    ]]>
    阿里云人工智能+大数据的实践与应用 — 阿里云开发者DevUP 沙龙·厦门站 Fri, 20 Jun 2025 02:20:33 +0800 阿里云开发者社区携手云原生后端、阿里云基础产品事业部共同出品了本次沙龙。

    现场邀请了4位阿里云专家为大家分享云计算行业人工智能+大数据的实践与应用,期待您的参与。

    第2页.png

    ]]>
    无人机是如何崛起并改变市场需求的 Fri, 20 Jun 2025 02:20:33 +0800



    了解多年来无人机的市场、可用性和需求是如何上升的。



    电影里虚构的世界里,空中已经布满了成群的无人机。但是,在现实生活中,我们离实现这一现实目标还差得远。这给我们带来了一个问题:无人机是会继续存在,还是会成为另一种城市炒作的一部分?不管未来会发生什么,目前,这些自动驾驶汽车在带来第四次工业革命方面也起到了重要作用。普华永道的一份题为《建立无人机信任》的报告显示,近31%的英国公众对无人机技术持积极态度,而在商业领域,这一比例上升至56%。该公司相信,通过教育,可以赢得公众和企业的信任,也可以改变人们对无人机的态度。欧盟表示,到2025年,该市场在欧洲的价值将达到10亿欧元。


    当无人机首次进入未知的天空时,情况并没有这么乐观。历史记载指出,它们主要用于军事和战争原因。幸运的是,这种情况随时间而改变,目前由于他们能够到达无法到达的地方;无人机被用于造福人类。包括搜救、运输、交通监测、农业测绘、消防等。


    image


    无人机:介绍


    什么是无人机?用外行人的话说,它是一种无人驾驶飞行器或无人机,可以被认为是飞行机器人。它可以远程控制,也可以使用嵌入GPS和传感器等系统的软件控制飞行计划自主飞行。无人机配备了双全球导航卫星系统(GNSS),其中包括GPS和GLONASS。它们也可以在非卫星模式下飞行。雷达定位等其他定位功能有助于无人机精确导航,还可以显示无人机与控制器之间的当前位置,而返回Home功能则引导无人机回到控制器。陀螺仪稳定技术促进了平稳飞行和障碍物检测,避碰技术保证了安全。此外,陀螺仪可以为中央飞行控制器提供重要的导航数据。


    无人机:需求增长的原因


    尽管高昂的成本和技术限制阻碍了无人机的广泛使用,但在提供扫描图像方面,无人机现在是最受青睐的资产。以前,我们依赖卫星图像,但现在由于成本、数据共享和时间方面的一些限制,不可能在日常用途上持续依赖卫星图像。相比之下,无人机可以提供实时、高分辨率的图像数据,其细节要精确得多,图像清晰度也很低。现在,由于小型化传感器成本的降低,即使是普通人也能拥有它们。此外,它还有助于快速决策,减少访问现场的次数。甚至传感器的进步也在数据质量和自动化方面产生了主要影响。此外,如今运行在人工智能和机器学习上的无人机,使其能够与建筑物或地面保持一致的距离,以帮助自动飞行,并提高测量精度。


    无人机的应用


    让我们看看一些无人机的流行使用案例。



    • 医疗:总部位于加州的Zipline利用位于加纳的一系列无人机配送中心,24小时为该国人口提供疫苗和药品。
    • 房地产:通过捕捉高价值房产的图片,无人机可以颠覆房地产行业。位于洛杉矶郊外的媒体公司ZawStudios使用无人机在大房子里拍摄360度身临其境的照片和视频。成品为潜在买家提供了一个模拟实际演练的视角。
    • 野生动物和森林保护:他们可以协助追踪非法活动,监控动物,统计其数量,计划重新造林,拍摄奇异的图片等等。此外,它们还有助于评估森林健康、侵占、砍伐、森林火灾、偷猎以及水体、生物多样性保护和红树林保护的状况。例如NETRAProUAV,eBeeX,SnotBot,AirShepherd。
    • 能源:sunpower正在使用无人机改进太阳能发电厂。SkySpecs是一家初创公司,该公司使用这些无人机对风力涡轮机进行检查,这在过去只需几分钟就能完成数小时的工作。
    • 天气预报:气象无人机可以收集和提供实时天气数据,帮助气象学家预测未来几天或几周的天气状况,并发布早期风暴警报。例如,Saildrone的自主帆船可以从海洋表面收集海洋和大气数据。
    • 保险:这些形式的无人机在穿越区域和角度时可以覆盖更多的地面,这对人力保险检查员来说是一个挑战。位于加州的公司DroneBase使用无人机对建筑物屋顶进行空中勘测,为保险公司评估与索赔相关的损失提供了一种简单的方法。
    • 互联网:可以向偏远地区提供低成本的互联网服务。最著名的例子就是Facebook的太阳能无人机Aquila。
    • 农业和再造林:通过无人机,农业工人可以收集数据,自动化冗余流程,提高效率。猛禽地图使用无人机帮助农民更好地预测他们的潜在收成。创业公司BiocarbonEngineering使用无人机在仰光南部种植红树林树苗。

    除了上述应用之外,无人机还可用于喷洒农药(DJI)、应急响应、搜救、采矿、消防、输油管道空中检查、城市规划、娱乐、旅游和旅游建设、货物配送等。


    我们能做些什么?


    虽然监管是必要的,但它们可能为无人机未来的发展构成障碍。仅通过将高度限制提高到几米,无人机就可以帮助农民以更低的成本将检查区域扩大一倍,并带来更好的效益。虽然目前允许无人机在视线以外和夜间操作,但在减少无人机日常使用的法规方面需要做很多工作。正因为规则灵活,中国和美国目前在这个市场上处于领先地位。





    本文作者:Cassie编译



    ]]>
    航空企业如何用「AI利器」提升乘客体验 Fri, 20 Jun 2025 02:20:33 +0800


     



    “早上好,Jones小姐,欢迎您乘坐从伦敦盖特威克机场买入巴黎的这趟「新常态」航班。请花一点时间,通过您手头的设备或座位背面的提示信息访问我们的门户网站。您可以在这里订购任何食物与饮料,我们的乘务员将立即将其送达。要不要来份您平时最喜欢的拿铁加鸡蛋卷套餐?——点击此处再来一份。”



    liQmGilXreIew_600


    航空公司掌握的客户数据可以说远超任何其他领域,而且这些信息当中蕴藏着对于运营、效率以及服务有着深远影响的高价值情报。然而,目前大部分航空企业仍然沿用着上世纪八十年代旅游繁盛期遗留下来的传统零售模式。因此,航空公司仍然很难真正捕捉到大量数据,并以有意义的方式将其用于生成洞见。最终,在飞机降落之后,飞机上发生的一切都只能短暂存在于机组人员的头脑当中,很快随着下一趟航班的开启而烟消云散。


    但是,时代在变化,COVID-19疫情的全面来袭给航空企业的利润带来沉重打击,也增强了人们对于自动式、无缝化候机与飞行体验的迫切渴望。另外,技术行业还开发出创新型机载系统,帮助航空企业在捕捉大量乘客数据的同时,为远距离乘客提供各类服务。


    利用这些创新成果以及对大数据的洞察能力,航空公司将获得超越竞争对手的巨大优势。从预订、值机、登机到乘机,航空公司能够全程跟踪客户群体的各类信息。具体来讲,航空企业不仅能够轻松发现销售情况最好的产品,还可以使用大数据深入理解客户的购买习惯,在数千条航线上在正确的时间与地点向乘客宣传最符合喜好的产品。将历史信息汇总起来,航空公司将能够预测客户行为并进行建模,生成个性化结果并推动未来的机票销售。


    然而,这一切目前还只是初步愿景。相当一部分航空公司已经挣扎在生存的边缘,需要说服外部投资者暂缓撤资。谁能在这场危机当中逆势成功,谁就必然能在下一个时代中处于航空服务的最前沿。


    Amazon教会我们什么是真正的数据之力


    Amazon之所以能够在网络购物领域一骑绝尘,依靠的正是“一家店面、包罗万有”的基本思路。很多客户其实并不太清楚自己到底想要什么、哪款产品最值得购买,而Amazon的工作就是为他们提供个性化建议,结合以往的喜好整理出最贴合心思的商品推荐。


    之所以能够做到这一点,是因为Amazon会在客户的浏览与购物过程中不断收集数据。Amazon对企业越了解,对于未来产品购买的预测就越是准确。另外,一旦明确了购物需求,Amazon还可以简化引导购买的流程。


    具体来说,Amazon的推荐技术基于协同过滤,即首先为用户画像以建立对买家的初步认知,而后根据这份类似于个人资料的素材推荐理想产品。


    除了购买操作之外,Amazon还会记录用户浏览过的内容、送货地址以及是否写过评论/反馈。他们甚至可以找到确切适合同类客户细分市场的其他产品,并根据其他同类客户的查询内容为您组织推荐内容,可以说是相当智能。


    航空行业为什么做不到?


    畅想了美好的前景,再回头看看惨淡的现实——机上零售领域的Amazon模式,似乎还离我们很远很远。


    考虑到飞机内的基本条件,我们也不难理解为什么围绕预测与分析展开的创新速度总是进度缓慢。旅客数据往往有着明确的轻量化特性,而且除了当前航班上的少量预见、备注饮食要求或者历史购买记录之外,航空公司很难了解机上乘客的偏好或者期望。另外,飞机的运载容量非常宝贵,难以储备充足的库存,只能根据线路存放一些与目的地相关的纪念品等小物件。乘客在旅途中浏览机上商品目录,一切购买活动都记录为纸质订单的形式。人气最高的商品很快销售一空,买不到的乘客沮丧不已……这就是现实情况,改变的出路在哪里?


    另一项非常重要的事实在于,目前只有十分之一的乘客选择在机上进行免税购物,相当于每趟短途航班只能完成10到12笔销售,而长途航班销售量则为30到40笔。另外,这个数字也在逐年缓慢下降。人们普遍认为这是受到选择范围有限、间接费用过高以及配送流程复杂等因素的影响。毕竟除了库存之外,机上的巡回手推车甚至是产品目录本身,都会占用相当一部分飞机运力。换言之,任何没有售出的产品都会带来高额成本,库存管理也因难以预测与及时补充而变得艰巨万分。


    归根结底,无论蕴藏着怎样的机遇,机上零售业务一直没有得到多少重视,直到现在......


    COVID-19疫情带来的挑战


    在COVID-19疫情的冲击之下,航空公司出于健康安全的诉求而开始重视大数据之力。很明显,这既能显著提升机上卫生标准,也有助于缓解日益紧缩的运营利润压力。


    根据Black Swan与APEX发布的最新报告,疫情爆发之后,乘客的信心受到严重影响,超过850万段社交对话开始传递负面情绪——数量在过去两个月中增长了43%。根据Black Swan的Will Cooper所言,卫生问题已经成为推动乘客负面情绪的三大主因之一。为此,航空公司必须增加技术在客户体验中的占比,这不仅有助于捕捉重要的客户数据,同时也将帮助航空运营商表现出改善并解决旅客焦虑心态的坚定意愿。


    如今,乘客在旅途当中希望获得无缝且高效的体验。航空公司有责任为乘客及机组人员提供这种尽可能减少接触的出行方式。实际上,最近与航空公司进行的一系列沟通表明,限制机组人员与乘客之间的接触,也已经成为航空公司员工正常复工的一项基本前提。在这方面,只有技术能够真正在双方诉求之间取得平衡点。


    Gategroup首席商务官Federico Germani在最近的新闻稿中表示,“对于航空公司而言,目前最重要的一步是在世界范围内重启航空服务,同时保障乘客与机组人员的健康安全。航空公司正在努力寻求在这种全新运营环境下行之有效的零售技术。”


    考虑到这些压力,航空公司开始考虑利用这一难得的机队停飞机会,立足实际环境推动技术测试。最具远见的各航空公司已经在制定相关计划,着手与技术及零售供应商合作展开探索。


    “我们需要谷歌级别的运营能力”


    关注像Amazon这样的企业,我们可以看到互联网如何改变消费者的购物习惯与具体方式。而其中蕴藏的潜力,完全有可能重塑客户们在空中的购物习惯。


    Gategroup/Black Swan的TRT Epax解决方案等订购与库存系统一直不断发展,伴随着飞机联网能力的改善,使得航空公司能够部署端到端方案,在持续服务客户的同时沿途捕捉相关数据。这一切不仅有助于消除乘客对于卫生防疫效果的顾虑,同时也让航空公司探索出一条在飞机上收集并使用数据的可行道路。


    航空企业乘客体验协会(APEX)首席执行官Joe Leader表示,“我们需要谷歌级别的运营能力。”如果航空公司真能将技术运用得如此纯熟,那么AI应用将变得更加轻松,并最终创造出空中版本的Amazon零售体验。


    对于航空这一向来行动缓慢的行业来说,此次变革无疑代表着激动人心的一步。而任何无视时代潮流、历史方向的从业方,都将被每位乘客乃至整个时代所抛弃。


     



    本文作者:Forbes


    ]]>
    除了视频分析,人工智能和机器学习还有什么好处? Fri, 20 Jun 2025 02:20:33 +0800

    --------点击屏幕右侧或者屏幕底部“+订阅”,关注我,随时分享机器智能最新行业动态及技术干货----------

    image.png

    人工智能(AI)和机器学习(ML)在物理安全市场上引起了轰动,将视频分析提升到了新的准确性水平。实际上,这些术语已成为整个行业的通用流行语。但是,人工智能和机器学习对物理安全行业产生影响的潜力远远超出了他们改善视频分析的能力。

    我们在本周的专家小组圆桌会议主题为:除了更好的视频分析之外,人工智能或机器学习如何使物理安全市场受益?

    Nigel Waterton-Chief Revenue Officer, Arcules

    虽然我确实认为,要与当今电影中的AI驱动结果相去甚远,但是添加这些算法可以大大帮助企业领导者最终做出更好的决策并降低风险。除了视频分析之外,这一目标也是该细分市场发展的核心。将另一种工具(例如基于云的功能)叠加到此智能上,可带来额外的优势和额外的灵活性,这是我们行业以前从未见过的。

    归根结底,这项技术对物理安全行业的真正好处是能够获取从各种物联网(IoT)设备传入的数据,并使用该信息建立业务运营的最佳实践,从而巩固自己的实力,并使组织更加了解组织面临的风险。

    Per Björkdahl-Chairman, ONVIF

    用户可以通过使用 AI,或更具体地说,通过深度学习和机器学习功能,以更高的效率和准确性来利用视频分析。这些术语尽管有时可以互换使用,但每个都有不同的优点。机器学习可提供更好、更准确的事件检测分析。人们通常在想到视频分析时,会将其与面部识别相关联。但是,机器学习功能远远不止于此,它可以监视运动及过程,以及检测流量和事件。

    相比之下,人工智能被用来模仿一个人可以做什么,并有助于某些低级任务的改进。物理安全中的 AI 旨在补充人员能力的终结。 AI 有助于改善自动决策和警报。

    Sean Foley-SVP, National Accounts, Interface Security Systems LLC

    我们对视频分析中的 AI 革命感到兴奋,但 AI 的应用不仅仅局限于视频。 AI 的真正力量在于处理大量通常是不同的数据集,以产生可行的见解。例如,资产保护专业人员对什么样的销售点交易是欺诈的危险信号有深刻的了解。 AI 可以将这种理解提升到指数级,在数千名员工中评估数百万笔交易,以在流程的早期(甚至是在发生之前)识别欺诈行为,从而减少收缩。

    同时,还可以将相同类型的 AI 模式识别应用于减少中央站的误报,或对系统故障进行超准确的预测,以改善客户服务。我们的行业才刚刚开始将几乎无法理解的数据与AI引擎和算法配对。应用是无限的,客户将因此受益。

    Stuart Rawling-Vice President of Technology and Customer Engagement, Pelco, Inc

    通过深度学习和应用于视频的其他 AI 驱动技术来提高智能的真正可能性是,从长远来看,直到事情发生后,我们才开始观看视频。通过视频收集这种高水平智能的目标可能会自动实现,以至于不需要安全操作员做出响应所需的决策。取而代之的是,由情报驱动的下一步将自动传达给各个利益相关者-从现场警卫到当地警察/消防部门。

    相反,当安全主管访问与事件对应的视频时,这是因为他们希望自己查看事件。自动化、简化响应的能力以及即时响应不是整体的、数据丰富的监视策略的目标吗?对于几乎所有企业来说,答案都是肯定的。

    Aaron Saks-Product and Technical Manager, Hanwha Techwin America

    除了更好的视频分析之外,人工智能(AI)或机器学习还可以极大地有益于物理安全市场。对于摄像机而言,人工智能不仅可以通过基于运动的分析消除误报,还可以做更多的事情。从自动化任务到运行例行程序以及比较数据,人工智能和深度学习都有可能改变我们使用安全摄像机的方式。

    由于安装的摄像头远远超过了人类可以监控的数量,为了利用所有这些信息,我们需要 AI 来理解我们正在收集的新数据,并告诉我们应该注意什么。我们想知道异常情况:那辆车在街上走错路了吗?公路中间有人吗?这些设备是功能强大的新型 IoT 传感器,可直接增强业务和运营。

    Adam Wynne-Software Engineering Manager, Security and Safety Things GmbH

    人工智能(AI)和机器学习还可以通过改进的访问控制系统以及将结果数据与其他设备集成来使物理安全市场受益。通过使用该技术,算法可以通过生物识别来识别个人并将其与安全摄像机自动集成,以开发更全面的访问控制解决方案。 AI 可以通过提高识别速度和准确性来增强生物指纹系统。此外,人工智能和机器学习还带来了实时检测复杂事件的附加好处,以前只有在事实分析之后才能将其作为取证分析的一部分。这使物理安全系统和响应变得更加简化和复杂。

    Jonathan Moore-Product Director, AMAG Technology, Inc.

    视频分析通常用于识别人和其他物体,然后触发特定的动作,例如打开门或触发警报。尽管此功能很有用,但数据分析具有巨大的价值,可以提供从访问控制系统中存储的大量数据中提取的有用见解。人工智能可以“学习”每个用户的典型访问模式,并在检测到可能对组织构成威胁的可疑或异常行为时警告安全性。

    除了检测潜在的危险活动之外,数据分析还可以用于更好地了解建筑物的占用和流量模式,以帮助实施物理疏离,突出显示配置错误或可能会发生故障的面板和设备等。数据分析程序可以帮助企业提高其安全性和内部威胁程序,了解其设施使用情况和流量模式以及优化其安全硬件。

    John Davies-Managing Director, TDSi

    人工智能(无论是真正的人工智能还是复杂的机器学习)在辅助物理安全方面具有巨大的潜力。通过学习和改善自己的数据,AI 可以快速确定什么是正常或异常行为,以便在早期发现潜在问题。视频分析的好处已被充分证明,但集中式安全系统中的 AI 可以监视范围更广的复杂数据。例如,在繁忙的机场或火车站中,中央 AI 系统可以处理人员进出安全区域的活动(使用访问控制以及视频监视),并找到可能提示拥塞问题或可疑行为的模式。

    此外,我们还看到越来越多的人工智能在诸如无人机之类的尖端技术中使用,它可以在无需人工指导的情况下确定远程安装,电力线或煤气管道是否存在任何问题或需要注意。

    Brian Baker-Vice President, Americas, Calipsa

    人工智能和机器学习给物理安全处理从摄像机和传感器输入的方式带来了指数式变化。数据是提供给 AI 的燃料,摄像机提供了大量的视频以供查看。 AI 的深度学习算法可自动检测出人类和车辆运动之间的差异,而不是动物、吹树叶或反射光。结果是大大减少了误报和潜在的相关罚款。

    我们将 AI 视为增加的安全层,帮助而不是代替人类来更好地保护人员和资产。使用人工智能,中央监控站或企业安全运营中心的操作员可以将注意力集中在真实警报上,以改善安全响应。通过减少虚假警报浪费的时间,管理人员可以在不增加人员的情况下扩展操作。如今,基于云的 AI 软件解决方案几乎在世界任何地方都将其功能添加到兼容摄像机中。

    总结

    人工智能(AI)和机器学习提供了有用的工具,可以理解大量的物联网(IoT)数据。 通过帮助实现低级决策的自动化,这些技术可以使安全运营商更加高效。 智能功能可以扩展集成选项,例如通过访问控制来增加生物识别的使用。 人工智能还可以帮助改善监控机制和流程。 智能系统可以帮助最终用户了解建筑物的占用和交通模式,甚至可以帮助实现物理距离。 以上这些只是技术的几种可能用途 —— 最后,一切皆有可能。

    image.png

    原文链接:https://ai.51cto.com/art/202008/623391.htm
    文章转自51cto,本文一切观点和《机器智能技术》圈子无关

    ]]>
    《Java开发手册》解读:大整数传输为何禁用Long类型? Fri, 20 Jun 2025 02:20:33 +0800 image.png
    8月3日,这个在我等码农心中具有一定纪念意义的日子里,《Java开发手册》发布了嵩山版。每次发布我都特别期待,因为总能找到一些程序员不得不重视的“血淋淋的巨坑”。比如这次,嵩山版中新增的模块——前后端规约,其中一条禁止服务端在超大整数下使用Long类型作为返回。

    image.png

    这个问题,我在实际开发中遇到过,所以印象也特别深。如果在业务初期没有评估到这一点,将订单ID这类关键信息,按照Long类型返回给前端,可能会在业务中后期高速发展阶段,突然暴雷,导致严重的业务故障。期望大家能够重视。

    这条规约给出了直接明确的避坑指导,但要充分理解背后的原理,知其所以然,还有很多点要思考。首先,我们来看几个问题,如果能说出所有问题的细节,就可直接跳过了,否则下文还是值得一看的:

    • 一问:JS的Number类型能安全表达的最大整型数值是多少?为什么(注意要求更严,是安全表达)?
    • 二问:在Long取值范围内,2的指数次整数转换为JS的Number类型,不会有精度丢失,但能放心使用么?
    • 三问:我们一般都知道十进制数转二进制浮点数有可能会出现精度丢失,但精度丢失具体怎么发生的?
    • 四问:如果不幸中招,服务端正在使用Long类型作为大整数的返回,有哪些办法解决?

    基础回顾

    在解答上面这些问题前,先介绍本文涉及到的重要基础:IEEE754浮点数标准。如果大家对IEEE754的细节烂熟于心的话,可以跳过本段内容,直接看下一段,问题解答部分。

    当前业界流行的浮点数标准是IEEE754,该标准规定了4种浮点数类型:单精度、双精度、延伸单精度、延伸双精度。前两种类型是最常用的。我们单介绍一下双精度,掌握双精度,自然就了解了单精度(而且上述问题场景也是涉及双精度)。

    双精度分配了8个字节,总共64位,从左至右划分是1位符号、11位指数、52位有效数字。如下图所示,以0.7为例,展示了双精度浮点数的存储方式。

    image.png

    存储位分配

    1)符号位:在最高二进制位上分配1位表示浮点数的符号,0表示正数,1表示负数。

    2)指数:也叫阶码位。

    在符号位右侧分配11位用来存储指数,IEEE754标准规定阶码位存储的是指数对应的移码,而不是指数的原码或补码。根据计算机组成原理中对移码的定义可知,移码是将一个真值在数轴上正向平移一个偏移量之后得到的,即[x]移=x+2^(n-1)(n为x的二进制位数,含符号位)。移码的几何意义是把真值映射到一个正数域,其特点是可以直观地反映两个真值的大小,即移码大的真值也大。基于这个特点,对计算机来说用移码比较两个真值的大小非常简单,只要高位对齐后逐个比较即可,不用考虑负号的问题,这也是阶码会采用移码表示的原因所在。

    由于阶码实际存储的是指数的移码,所以指数与阶码之间的换算关系就是指数与它的移码之间的换算关系。假设指数的真值为e,阶码为E ,则有 E = e + (2 ^ (n-1) - 1),其中 2 ^ (n-1) - 1 是IEEE754 标准规定的偏移量。则双精度下,偏移量为1023,11位二进制取值范围为[0,2047],因为全0是机器零、全1是无穷大都被当做特殊值处理,所以E的取值范围为[1,2046],减去偏移量,可得e的取值范围为[-1022,1023] 。

    3)有效数字:也叫尾数位。最右侧分配连续的52位用来存储有效数字,IEEE754标准规定尾数以原码表示。

    浮点数和十进制之间的转换

    在实际实现中,浮点数和十进制之间的转换规则有3种情况:

    1 规格化

    指数位不是全零,且不是全1时,有效数字最高位前默认增加1,不占用任何比特位。那么,转十进制计算公式为:

    (-1)^s*(1+m/2^52)*2^(E-1023)

    其中s为符号,m为尾数,E为阶码。比如上图中的0.7 :

    1)符号位:是0,代表正数。

    2)指数位:01111111110,转换为十进制,得阶码E为1022,则真值e=1022-1023=-1。

    3)有效数字:

    0110011001100110011001100110011001100110011001100110

    转换为十进制,尾数m为:1801439850948198。

    4)计算结果:

    (1+1801439850948198/2^52)*(2^-1) =0.6999999999999999555910790149937383830547332763671875

    经过显示优化算法后(在后文中详述),为0.7。

    2 非规格化

    指数位是全零时,有效数字最高位前默认为0。那么,转十进制计算公式:

    (-1)^s*(0+m/2^52)*2^(-1022)

    注意,指数位是-1022,而不是-1023,这是为了平滑有效数字最高位前没有1。比如非规格最小正值为:

    0x0.00000000000012^-1022=2^-52 2^-1022 = 4.9*10^-324

    3 特殊值

    指数全为1,有效数字全为0时,代表无穷大;有效数字不为0时,代表NaN(不是数字)。

    问题解答

    1 JS的Number类型能安全表达的最大整型数值是多少?为什么?

    规约中已经指出:

    在Long类型能表示的最大值是2的63次方-1,在取值范围之内,超过2的53次方(9007199254740992)的数值转化为JS的Number时,有些数值会有精度损失。

    “2的53次方”这个限制是怎么来的呢?如果看懂上文IEEE754基础回顾,不难得出:在浮点数规格化下,双精度浮点数的有效数字有52位,加上有效数字最高位前默认为1,共53位,所以JS的Number能保障无精度损失表达的最大整数是2的53次方。

    而这里的题问是:“能安全表达的最大整型”,安全表达的要求,除了能准确表达,还有正确比较。2^53=9007199254740992,实际上,

    9007199254740992+1 == 9007199254740992

    的比较结果为true。如下图所示:

    image.png

    这个测试结果足以说明2^53不是一个安全整数,因为它不能唯一确定一个自然整数,实际上9007199254740992、9007199254740993,都对应这个值。因此这个问题的答案是:2^53-1。

    2 在Long取值范围内,2的指数次整数转换为JS的Number类型,不会有精度丢失,但能放心使用么?

    规约中指出:

    在Long取值范围内,任何2的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数。

    后半句,我们就不说了,因为绝对没毛病,空间不限,不仅是任何整数可以精确表示,无理数我们也可以挑战一下。我们重点看前半句,根据本文前面所述基础回顾,双精度浮点数的指数取值范围为[-1022,1023],而指数是以2为底数。另外,双精度浮点数的取值范围,比Long大,所以,理论上Long型变量中2的指数次整数一定可以准确转换为JS的umber类型。但在JS中,实际情况,却是下面这样:

    image.png

    2的55次方的准确计算结果是:36028797018963968,而从上图可看到,JS的计算结果是:36028797018963970。而且直接输入36028797018963968,控制台显示结果是36028797018963970。

    这个测试结果,已经对本问题给出答案。为了确保程序准确,本文建议,在整数场景下,对于JS的Number类型使用,严格限制在2^53-1以内,最好还是信规约的,直接使用String类型。

    为什么会出现上面的测试现象呢?

    实际上,我们在程序中输入一个浮点数a,在输出得到a',会经历以下过程:

    1)输入时:按照IEEE754规则,将a存储。这个过程很有可能会发生精度损失。

    2)输出时:按照IEEE754规则,计算a对应的值。根据计算结果,寻找一个最短的十进制数a',且要保障a'不会和a隔壁浮点数的范围冲突。a隔壁浮点数是什么意思呢?由于存储位数是限定的,浮点数其实是一个离散的集合,两个紧邻的浮点数之间,还存在着无数的自然数字,无法表达。假设有f1、f2、f3三个升序浮点数,且它们之间的距离,不可能在拉近。则在这三个浮点数之间,按照范围来划分自然数。而浮点数输出的过程,就是在自己范围中找一个最适合的自然数,作为输出。如何找到最合适的自然数,这是一个比较复杂的浮点数输出算法,大家感兴趣的,可参考相关论文[1]。

    image.png

    所以,36028797018963968和36028797018963970这两个自然数,对应到计算机浮点数来说,其实是同一个存储结果,双精度浮点数无法区分它们,最终呈现哪一个十进制数,就看浮点数的输出算法了。下图这个例子可以说明这两个数字在浮点数中是相等的。另外,大家可以想想输入0.7,输出是0.7的问题,浮点数是无法精确存储0.7,输出却能够精确,也是因为有浮点数输出算法控制(特别注意,这个输出算法无法保证所有情况下,输入等于输出,它只是尽力确保输出符合正常的认知)。

    image.png

    扩展

    JS的Number类型既用来做整数计算、也用来做浮点数计算。其转换为String输出的规则也会影响我们使用,具体规则如下:
    image.png

    上面是一段典型的又臭又长但逻辑很严谨的描述,我总结了一个不是很严谨,但好理解的说法,大家可以参考一下:

    除了小数点前的数字位数(不算开始的0)少于22位,且绝对值大于等于1e-6的情况,其余都用科学计数法格式化输出。举例:

    image.png

    3 我们一般都知道十进制数转二进制浮点数有可能会出现精度丢失,精度丢失怎么发生的?

    通过前面IEEE754分析,我们知道十进制数存储到计算机,需要转换为二进制。有两种情况,会导致转换后精度损失:

    1)转换结果是无限循环数或无理数

    比如0.1转换成二进制为:

    0.0001 10011001100110011001100110011...

    其中0011在循环。将0.1转换为双精度浮点数二进制存储为:

    0 01111111011 1001100110011001100110011001100110011001100110011001

    按照本文前面所述基础回顾中的计算公式 (-1)^s(1+m/2^52)2^(E-1023)计算,可得转换回十进制为:0.09999999999999999。这里可以看出,浮点数有时是无法精确表达一个自然数,这个和十进制中1/3 =0.333333333333333...是一个道理。

    2)转换结果长度,超过有效数字位数,超过部分会被舍弃

    IEEE754默认是舍入到最近的值,如果“舍”和“入”一样接近,那么取结果为偶数的选择。

    另外,在浮点数计算过程中,也可能引起精度丢失。比如,浮点数加减运算执行步骤分为:

    零值检测 -> 对阶操作 -> 尾数求和 -> 结果规格化 -> 结果舍入

    其中对阶和规格化都有可能造成精度损失:

    • 对阶:是通过尾数右移(左移会导致高位被移出,误差更大,所以只能是右移),将小指数改成大指数,达到指数阶码对齐的效果,而右移出的位,会作为保护位暂存,在结果舍入中处理,这一步有可能导致精度丢失。
    • 规格化:是为了保障计算结果的尾数最高位是1,视情况有可能会出现右规,即将尾数右移,从而导致精度丢失。

    4 如果不幸中招,服务端正在使用Long类型作为大整数的返回,有哪些办法解决?

    需要分情况。

    1)通过Web的ajax异步接口,以Json串的形式返回给前端

    方案一:如果,返回Long型所在的POJO对象在其他地方无使用,那么可以将后端的Long型直接修改成String型。

    方案二:如果,返回给前端的Json串是将一个POJO对象Json序列化而来,并且这个POJO对象还在其他地方使用,而无法直接将其中的Long型属性直接改为String,那么可以采用以下方式:

    String orderDetailString = JSON.toJSONString(orderVO, SerializerFeature.BrowserCompatible);

    SerializerFeature.BrowserCompatible 可以自动将数值变成字符串返回,解决精度问题。

    方案三:如果,上述两种方式都不适合,那么这种方式就需要后端返回一个新的String类型,前端使用新的,并后续上线后下掉老的Long型(推荐使用该方式,因为可以明确使用String型,防止后续误用Long型)。

    2)使用node的方式,直接通过调用后端接口的方式获取

    方案一:使用npm的js-2-java的 java.Long(orderId) 方法兼容一下。

    方案二:后端接口返回一个新的String类型的订单ID,前端使用新的属性字段(推荐使用,防止后续踩坑)。

    引用

    [1]http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.52.2247&rank=2
    [2]《码出高效》

    ]]>
    阿里云推出小白建站:模板与定制如何选择 Fri, 20 Jun 2025 02:20:33 +0800 企业建站不仅是企业形象的重要组成部分,更是将自身展示给客户乃至全球的窗口,因此越来越多的企业会选择建立自己的网站,因此,现在阿里云的建站产品也越来越火了,阿里云建站产品主要分为模板建站和定制建站,模板建站指的是云·速成美站,定制建站指的是云·企业官网。

    阿里云建站产品产品详情:
    1.模板建站(云·速成美站)
    2.定制建站(云·企业官网)
    image.png

    模板建站(云·速成美站)
    云·速成美站分为基础版、标准版、企业版三个版本,主要特点是价格相对便宜,一般网站有的功能,云·速成美站都有。使用阿里云提供上千套模板,可视化后台管理,会打字就会建站。
    定制建站(云·企业官网)
    云·企业官网主要分为标准版、高级版、尊贵版三个版本,主要特点是有专门的建站专家一对一服务,可以根据用户的需求定制各种功能。

    模板建站与定制建站怎么选
    首先,要看自己会不会做网站,是否有建站能力
    模板建站适合有一定网站制作能力的个人或小企业用户,模板建站支持Web站点、移动端站点、互动表单以及会员支付多场景。
    定制建站适合对网站有品质要求或个性化需求、希望节省人力和时间成本的企业用户。
    简单来说:自己会做网站,且阿里云提供的模板建站功能满足自己的需求,那么选择模板建站即可,如果自己不做,完全没接触过网站设计,或者模板建站的功能无法满足自己需求,那么就选择定制建站。

    其次,要看功能是否满足自己需求
    例如,你希望自己的网站有双语、官网电商、大容量(空间)、有CDN加速,网站秒速打开等功能则必须选择定制建站。

    最后,看预算
    模板建站官方最低报价是500元,定制建站官方最低报价是4980元,模板建站的价格相对定制建站便宜很多,因此,在模板建站功能能满足自己需求的同时,自己会一点建站能力或者原因学习建站,推荐选择模板建站更实惠。

    模板建站与定制建站版本选择注意事项:
    如果你选择的是模板建站,需要注意:
    1.基础版支持制作页面数7个,支持上传图片数量100个,支持上传文章及产品各20个,支持制作表单数量1个,而标准版和企业版则没有这个限制。
    2.企业版为独立IP,基础版标准版为共享IP。

    如果你选择的是定制建站,需要注意:
    1.标准版为PC站+手机站,高级版为PC站+手机站+双语,尊贵版为PC站+手机站+官网电商。
    2.页面制作和新闻/产品预置数量上标准版为10个,高级版为15个,尊贵版为20个。
    3.标准版的网站空间为200G,高级版为500G,尊贵版则不限制空间大小。

    最后,赠送福利:
    阿里云最近上架了全新的建站产品满减券,总额为3200元,具体面额如图所示:
    image.png

    建站满建站领取地址:阿里云小站
    领取之后,支付建站产品订单的时候,系统会根据订单金额自动匹配可使用的满减券面额,这样购买更加便宜些。
    image.png
    原文地址

    ]]>
    电商搜索“无结果率高,体验差”怎么办? Fri, 20 Jun 2025 02:20:33 +0800 垂直电商与综合类电商相比,具有更精准的市场定位、更深化的产品与服务质量、更强的客户粘性和更独特的品牌附加度的优势,所以搜索性能的好坏直接影响着业务最终结果

    案例背景:

    某日活千万级的垂直类电商平台,业务以社区+商城形式开展,商城业务是商业收入的主要来源,大部分用户有明确的购买指向性,其中商品搜索天级PV3000万+,搜索引导的成交占比全部成交的60%以上,是站内最重要的功能,在用户满意度调研中发现对搜索体验吐槽连连,反馈的主要问题是站内商品搜不到,个人卖家发布的商品排序靠后

    搜索问题反馈

    (1)用户:搜索不到想要的商品,体验差;
    (2)运营: 站内搜索的无结果率接近60%,说明每天有1800万的PV转换为0,流量白白浪费;
    1597059169694-cf863434-8e08-45b0-8689-f4c4373d2909.png

    (3)个人卖家: 个人卖家发布的商品排序靠后;打击发布商品积极性,影响平台价值定位和圈层生态,从而直接影响平台收益;

    搜索问题成因:

    (1)垂直小众的圈子,对于商品的叫法非常多样,并形成主流,用户搜索中不一定按照实际商品名称进行查询(例如:用户会搜“喷泡”其实想找的商品是Air Jordan AirFoamposite系列的鞋);
    (2)用户搜索表述错误(例如:搜“连衣群”其实是想搜“连衣裙”);
    (3)站内的搜索结果分3个tab呈现,分别为“销量”、“价格”、“新品”,用户搜索后默认展现的是“销量”tab下的结果,因此个人卖家发布产品由于销量少或无销量自然导致排序靠后,曝光量小,销量难增长,恶性循环;

    问题分析:

    (1)针对召回结果不理想情况,经分析发现自建ES服务没有对搜索关键词做智能的语义理解,甚至有些实体名词分词还是错误的;
    (2)针对排序问题,经分析需增加“综合”搜索结果呈现,根据核心索引优化排序算法;

    开放搜索解决方案:

    1595474121408-2e973285-9869-47c1-b46e-3c85c4cf4526.png
    (1)核心索引上配置使用了电商行业的查询语义理解,包括同义词、停用词、电商拼写纠错、电商实体识别等 ,就是这些功能将搜索关键词进行了系统可识别的智能改写,扩大召回相关结果;
    (2)针对商品别称问题,运营同学通过平时运营积累的专业词汇可视化同步到开放搜索做查询语义理解功能的补丁,通过灵活干预得以解决;
    (3)创建核心索引“商品标题、颜色、类目名称、品牌名称、运营优化文案、系列名称等”,将它们引入到排序表达式中,通过多个维度构建出更精细化的排序模型;
    (4)增加“综合”搜索tab,并默认展示“综合”搜索结果

    实践后的搜索性能对比:

    (1)搜索“詹姆斯球衣”输入成“詹慕斯球衣”
    • Before: 服务无法召回相关结果;
    • After: 纠错改写为“詹姆斯”进行查询,并且前端会提示“以下的结果是查询:詹姆斯球衣,仍然搜索詹慕斯球衣”;IMG_5180.PNG
    (2)搜索“喷泡”
    • Before:无法召回相关结果;
    • After: 召回到Air Jordan AirFoamposite系列的鞋
    1597058789960-e2f3639e-fc98-4d87-a9f1-4da9c9322289.jpeg
    (3)排序效果
    • Before:以销量默认排名,个人卖家排序靠后
    • After:提高搜索相关性增加更多商品曝光机会

    ]]>
    DevUP 沙龙 | 八月北京、青岛、厦门燥起来 Fri, 20 Jun 2025 02:20:33 +0800 炎热的周末不知去哪里耍,不如来参加阿里云开发者 DevUP 沙龙吧。三大城市火热来袭,专家面对面交流,更有实操环节加深理解。放松学习的同时还有机会结交志同道合的小伙伴,约约约!

    报名直通车:

    8月22日【青岛】活动:阿里云开发者 DevUP 沙龙 -青岛站 -阿里巴巴微服务技术的应用与实践

    8月22日【厦门】活动:阿里云人工智能+大数据的实践与应用-阿里云开发者DevUP 沙龙·厦门站

    8月27日【北京】活动:阿里云开发者 DevUP 沙龙 -北京站 -阿里云企业AIOT技术与解决方案沙龙

    大咖分享还有动手操作,更多精彩尽在【阿里云开发者 DevUP 沙龙】。活动时间均在周末,放松学习同时还有机会结交志同道合的小伙伴,还等什么快来参加吧<<<<<<<

    活动一:阿里云开发者 DevUP 沙龙 -青岛站 -阿里巴巴微服务技术的应用与实践

    微服务开发中,SpringCloud作为Spring生态中的针对微服务的技术框架,越来越受到各个企业技术人员的追捧。但是,SpringCloud中一些组件,在实践使用中,存在一定的局限。SpringCloudAlibaba,横空出世,替换了SpringCloud中的一些组件,使微服务在实践中,能够更便捷的、更优雅的实现落地。

    8月15日,上海ACE同城会特邀阿里平头哥的2位语音产品专家,从操作系统融合、解决方案支撑、语音市场趋势规划等方面,跟大家一起畅聊语音圈!

    时间:8月22日(周四)13:30-16:10
    地点:山东省青岛市崂山区海尔路170号鑫裕和大厦8楼

    活动亮点:
    1、面基!在青岛举办的线下技术沙龙。
    2、阵容!各类技术专家在线实践教学。
    3、干货!快速了解微服务相关知识。

    报名地址:https://survey.aliyun.com/apps/zhiliao/7irPO_3gv

    image.png

    活动二:阿里云人工智能+大数据的实践与应用-阿里云开发者DevUP 沙龙·厦门站

    阿里云开发者社区携手云原生后端、阿里云基础产品事业部共同出品了本次沙龙。

    现场邀请了4位阿里云专家为大家分享云计算行业人工智能+大数据的实践与应用,期待您的参与。

    时间:8月22日(周六)13:30——17:20
    地点:厦门市集美区杏林湾路营运中心1号楼2楼

    报名地址:https://developer.aliyun.com/article/769954

    image.png

    活动三:阿里云开发者 DevUP 沙龙 -北京站 -阿里云企业AIOT技术与解决方案沙龙

    在阿里云线上市场,近2万解决方案已经应用在几十万个城市,企业和工厂,无数家庭受天猫精灵带来的只能生活,物联网已经是基础设施,更是企业的核心竞争力。

    时间:8月27日(周四)13:30-17:30
    地点:北京市朝阳区望京东园4区4号楼——阿里中心·望京B座 2F-13文韵阁

    活动亮点:
    1、面基!在阿里园区举办的线下技术沙龙。
    2、阵容!阿里云各类技术专家在线教学。
    3、干货!快速打造企业级物联网平台和应用,实现数字化转型。

    报名地址:https://developer.aliyun.com/article/769911

    image.png

    各位小伙伴是不是按耐不住内心的激动了呢?快快点击下方的阅读原文报名,来和阿里巴巴的技术专家们深度交流吧!

    ACE同城会

    阿里云 ACE全称 Alibaba Cloud Engineer。意为阿里云的工程师、代表着云计算的建设者。同时“ACE”又是扑克牌中的“A”,因此阿里云ACE也寓意着是云计算领域王牌的一群人。

    ACE同城会是遍布在全国的开发者社群,作为国内优秀的开发者圈子,为所有开发者提供学习、交流的机会和平台。

    在全国48个城市成立了同城会,覆盖13万开发者,举办超100场线下活动。

    官网:https://mvp.aliyun.com/ace

    现开发者社区招募会长和班委,有意者钉钉扫码联系运营同学,加好友时请备注【ACE同城会会长/班委】。

    test

    ]]>
    macOS 神器 Workflow ,让效率翻倍! Fri, 20 Jun 2025 02:20:33 +0800 滚动.gif

    背景

    一起回顾一个大家非常熟悉场景。

    上周开发一个需求,开发过程中,我需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些debug的信息。所以我登录到跳板机,此时我发现我忘了某一台机器的具体名字(通常能记得的人都是天才),所以需要用跳板机提供的 autoget 命令来通过服务器组名来获得机器的列表。

    此时我发现我连服务器组名都忘了,所以需要上 eagle eye 上查寻一下组名。这个查询可能得依靠我的记忆打出组名的前缀,通过 eagle eye 给出的补全提示列表来识别出其中那个我需要的组名。

    终于我得到了我的组名,为了避免我下次再忘记而不得不再繁琐的查询一遍,我选择把这些结果记录到我的记事本中,方便我下次查找使用。终于我登录了机器,可是不巧我忘了日志记录的位置,我想起某个同事曾经告诉过我日志的路径,于是我查找了与那个同事的聊天记录,找到了这个日志路径。同样为了防止忘记,我又把它记录到了我的记事本中方便下次查找。然后我把日志路径复制粘贴到命令行,我终于可以开始工作了。

    昨天我又开发了一个需求,我还需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些 debug 的信息。所以我登录到跳板机,需要用跳板机提供的 autoget 命令来通过服务器组名来获得机器的列表,我想起了我在记事本中记录了这个内容,于是我打开记事本,搜索了 keyword,在简单的翻找下,我找到了我想要的命令。

    进入到机器后,我发现日志的路径我还是没有记住。于是,我再次打开记事本,搜索了另一个 keyword ,再次翻找了一下,找到路径后把它复制粘贴到命令行,我一边粘贴一边想,我要是像在本机的终端环境中,把这些命令写成 bashrc 中的 alias 就好了。我又可以开始工作了。

    于是,我的记事本中的内容一般都是这样:

    image.png

    从上述两次流程的对比中,我们发现记事本已经给我们的工作带来很大的提效了。但是在昨天流程最后我的思考中,我们不难发现,这个提效,还有提升的空间。我认为任何用过 shell 的 alias 的同学都会认同我的观点:如果上述流程能用alias来记录这些冗长的命令,我们就不用麻烦记事本了不是吗?

    可是跳板机是公共资源,有严格的使用规范。具体服务器又是容器化部署,每次部署都会是一个新的容器,所以在当前的bashrc上写下什么并没有用。或许我们能寻找一个新的途径来实现这个需求。

    问题

    让我们从背景中总结我们正在面临的是哪些问题:

    1、工作中存在非常多冗长难记的信息,需要我们在各个场景反复输入。
    2、这些冗长的信息来源分散,查找起来非常麻烦耗时。
    每次需要输入时等要通过额外的操作,频繁切换聚焦的窗口来获取这些信息。
    3、这些问题虽然各自都占用了我们为数不多的时间和精力,但因为场景小而频繁,当乘以次数后,这些消耗也变得非常可观,并且非常影响我们的工作体验。

    思考

    让我们思考一下当我们想要使用 alias 的时候我们实际想要的是什么?我以个人的经验来总结,大概是以下几点:

    1、用一个很短的短词来替代一个需要高频输入的很长的句子。

    2、用一个更好记的词替代一个难记易忘的句。

    3、配置的成本可控,使用的成本很低。

    我们品一品1、2两点,其本质就是一个字典,由短语为key,长句为value。这个是我们程序员的好朋友了,我们可以简单的通过一个文件就能实现这份配置。事实上,我们使用记事本记录,其实本质也是在使用字典的特性。而第三点,让我瞬间想到使用 Alfred 的 Workflow 这个Mac上的神器。关于 Alfred 此处不做介绍,不知道或者想要了解的同学可以移步官网

    设计

    其实针对我们已经给出的需求,我们非常容易就可以得出一个设计思路。我们可以固定一个文件路径保存一个文件,这个文件以一种简单的格式或方式保存一个字典。编写一个 alfred 的 workflow 来解析这个文件形成一个 Map ,并通过搜索和匹配 key 来快速的获取 value ,而获取 value 最有效的方式就是把 value 输出到系统的剪切板中。

    考虑到 Mac 和 Alfred 的使用用户并不全是工程师,我们选择记录字典的格式最好越简单越好。所以我计划以普通的文本格式,每一行为一个键值对,第一个空格前的短词为 key ,第一个空格后的内容为 value 。直接让用户新建指定路径的文件并通过编辑文件的方式来管理的形式确实可以被一部分用户所接受,但是为了能面对更多用户,我认为以 workflow 的方式在增删字典的内容也同时是需要支持的。这样不想关心具体实现、不愿接触文本文件的用户同样可以无感使用。

    最后我给这个 workflow 取名为 EasyAlias。

    实现

    来看一下 workflow 的排版:

    image.png

    通过三个关键字的Alfred命令,分别实现设置alias(sal, set alias),删除alias(dal, delete alias),查找(gal, get alias)。

    其中sal和dal使用简单的keyword输入,而gal为了使用Alfred通过的展示候选列表和搜索匹配的能力,而使用了Script Filter作为输入。三者都通过shell调用了一个实现主要功能的python脚本easy_alias.py,通过传入不同的action参数来区分行为。

    sal:

    python easy_alias.py set {query}

    dal:

    python easy_alias.py del {query}

    gal:

    python easy_alias.py show {query}
    cat filter.output

    easy_alias.py

    # coding=utf8
    import sys
    import json
    from os import listdir, makedirs
    from os.path import isfile, join, exists, expanduser
    
    base_path = expanduser("~/.easy_alias")
    file_name = "alias_conf"
    file_path = join(base_path, file_name)
    
    alias_map = dict()
    
    def init():
        if not exists(base_path):
            makedirs(base_path)
        if not exists(file_path):
            open(file_path, 'w').close()
    
    def get_key_and_value(text):
        seqs = text.strip().split(' ')
        if len(seqs) < 2:
            return None, None
        key = seqs[0];
        value = reduce(lambda x, y: x.strip() + ' ' + y.strip(), seqs[1:])
        return key, value
    
    def get_alias_map():
        with open(file_path, 'r') as f:
            for line in f.readlines():
                k, v = get_key_and_value(line)
                if k == None or v == None:
                    continue
                alias_map[k] = v
    
    def set_alias():
        if len(sys.argv) < 3:
            return 
        text = sys.argv[2].strip()
        k, v = get_key_and_value(text)
        if k == None or v == None:
            return
        alias_map[k] = v
    
    def del_alias():
        if len(sys.argv) < 3:
            return 
        key = sys.argv[2].strip()
        new_content = ""
        if key in alias_map:
            alias_map.pop(key)
    
    def show_alias():
        items = list()
        for k, v in alias_map.iteritems():
            d = {
                "uid": k,
                "type": "default",
                "title": k,
                "subtitle": v,
                "arg": v,
                "autocomplete": k,
                "icon": {
                    "type": "fileticon",
                    "path": "icon.png"
                }
            }
            items.append(d)
        show = {"items": items}
        with open('filter.output', 'w') as f:
            f.write(json.dumps(show))
    
    def write_map_to_file():
        file_content = ''
        for k, v in alias_map.iteritems():
            file_content += k + ' ' + v + 'n'
        with open(file_path, 'w') as f:
            f.write(file_content)
    
    if __name__ == '__main__':
        init()
        get_alias_map()
        action = sys.argv[1]
    
        with open(join(base_path, 'logs'), 'a') as f:
            f.write(str(sys.argv) + 'n')
    
        if (action == 'set'):
            set_alias()
        if (action == 'del'):
            del_alias()
        if (action == 'show'):
            show_alias()
    
        write_map_to_file()

    效果

    设置一个alias

    image.png

    查找一个alias

    image.png

    删除一个alias

    image.png

    如果觉得通过sal设置和dal删除的方式太麻烦,也可以直接编辑~/.easy_alias/alias_conf

    image.png

    保存文件再查询

    image.png

    作业

    这个 workflow 本身很简单很好实现。本文也希望不仅仅只是一个简单分享,希望能与读者有所互动,所以打算留个回家作业。

    可以发现现在 dal 命令现在需要使用者盲打key,而不是像gal这样可以搜索补全。这会给使用者带来一定烦恼。回家作业就是将 dal 命令也改造成像 gal 一样可以搜索补全的形式。

    作业下载
    EasyAliasPro

    总结

    让我们回过头看一下我们再开始时面对的问题是否得到了很好的解决。

    我们无法改变工作中频繁需要冗长信息的状况,但是我们通过访问剪切板的方式让输入变得简单。
    我们用一个文件将这些信息集中在一起,并且通过工具打打提升了我们检索这些信息的效率。
    Alfred提供给我们一个在检索并获取这些信息上无需切换窗口,并且操作非常简单的方式。

    很高兴,我们很大程度上解决了我们先前提出的这些问题!

    最后

    今天我又开发了一个需求,我还需要登录到服务器上看一下服务运行的日志,确认运行状态或者看一些debug的信息。所以我登录到跳板机,唤醒了Alfred,输入gal ahostp,并将结果粘贴在命令行获得了机器列表。登上机器后,我再次唤醒Alfred,输入gal alog,并粘贴在命令行中。现在我可以开始工作了。

    关注「淘系技术」微信公众号,一个有温度有内容的技术社区~

    image.png

    ]]>
    DataWorks百问百答51:如何用独享资源组跑ADB、DLA节点? Fri, 20 Jun 2025 02:20:33 +0800

    DataWorks通过ADB、DLA等节点类型访问对应数据源介绍     

       您可以在Dataworks中新建AnalyticDB for MySQL、AnalyticDB for PostgreSQ、Data Lake AnalyticsL等节点,构建在线ETL数据处理流程。
    (1)AnalyticDB for MySQL节点用于接入阿里云产品分析型数据库MySQL版,详情请参见分析型数据库MySQL版
    (2)AnalyticDB for PostgreSQL节点用于接入阿里云产品分析型数据库PostgreSQL版,详情请参见分析型数据库PostgreSQL版
    (3)Data Lake Analytics节点用于接入阿里云产品Data Lake Analytics,详情请参见什么是Data Lake Analytics

    以AnalyticDB for MySQL为例:

    1、首先需要在数据源配置界面,用连接串形式配置好数据源,jdbc地址用内网地址添加的话,在测试连通性时,默认是用默认资源组在访问,所以会存在网络不通的问题,因此,在我们配置完数据源信息后,可直接保存,跳过测试连通性,直接去建立AnalyticDB for MySQL节点任务。
    image.png


    image.png
    2、建立好节点之后,选择上方添加好的的数据源,编辑逻辑处理查询等语句,即可在线ETL数据处理。
    image.png


    3、编写语句完成之后,保存,然后点击高级运行,选择独享调度资源组运行即可。
    image.png


    4、出于安全的考虑,数据库会有白名单的限制,因为平台所提供的默认资源组机器IP不固定,时常会出现网络访问不通的情况,所以推荐大家使用独享调度资源组来跑任务(购买的独享调度资源和数据源需要在同VPC同可用区下)。
         用独享资源组跑任务时,需要将独享资源组的EIP、网段信息和独享资源绑定的专有网络的IP网段(或绑定专有网络时选择的交换机网段)加至对应数据源的访问许可内。没有添加的话,会出现以下网络不通的情况:
    Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    image.png

    注意:

    再次提醒:独享资源组必须要和数据源在同VPC同可用区下,也要添加相应的网段至数据库白名单中,这样网络方可访问成功。

    DataWorks百问百答历史记录 请点击这里查看>>

    更多DataWorks技术和产品信息,欢迎加入【DataWorks钉钉交流群】

    ]]>
    【最佳实践】3分钟学会使用Elasticsearch跨集群复制功能(CCR) Fri, 20 Jun 2025 02:20:33 +0800 当您需要将本地Elasticsearch集群中的索引数据迁移到一个远程集群中,或者将一个远程集群中的索引数据迁移到本地集群,可通过跨集群复制CCR(Cross Cluster Replication)功能实现。本文介绍具体的实现方法。

    背景信息


    CCR是开源Elasticsearch在platinum版本中发布的一个商业特性购买阿里云Elasticsearch实例后,您无需额外付费,只需要简单配置,即可使用CCR功能(目前仅支持单可用区6.7.0及以上版本的阿里云Elasticsearch实例)。CCR的应用场景如下:

    • 灾难恢复及高可用性对于分布在不同地域的Elasticsearch集群,您可以通过CCR进行数据备份。当其中一个集群发生故障时,您可以通过访问其他集群来获取故障集群的数据,保证数据不丢失。
    • 就近访问数据例如A集团下有多个子公司,各子公司所分布的地域不同。为了提高业务处理速度,可按照地理位置划分子公司要承担的业务,并通过CCR将业务数据分发给各地域中的Elasticsearch集群。子公司在处理业务时,可直接访问当前所在地域的集群。
    • 集中报告通过CCR,将多个数据量较小的集群中的数据复制到一个中央集群中,进行可视化分析与报告。

    使用CCR功能,需要准备两种类型的集群。一个是远程集群,即提供源数据(Leader index)的集群;一个是本地集群,即订阅数据(Follower index)的集群。该功能为被动复制,即所有复制任务都是由本地集群执行。同时支持批量实时迁移数据,更多详情请参见Cross-cluster replication

    本文以阿里云Elasticsearch为例,为您介绍跨集群复制功能(CCR)的使用方法。阿里云Elasticsearch兼容开源Elasticsearch的功能,以及Security、Machine Learning、Graph、APM等商业功能,致力于数据分析、数据搜索等场景服务。支持5.5.3、6.3.2、6.7.0、6.8.0和7.4.0等版本,并提供了商业插件X-Pack服务。在开源Elasticsearch的基础上提供企业级权限管控、安全监控告警、自动报表生成等功能。阿里云Elasticsearch为您提供1个月的免费试用活动,单击此处即可免费试用。

    操作流程


    1. 准备工作

    准备远程和本地集群,以及待迁移的索引。

    1. 步骤一:配置实例网络互通

    连通远程和本地集群的网络。

    1. 步骤二:添加远程集群

    在本地集群的Kibana控制台中,添加远程集群。

    1. 步骤三:配置跨集群复制

    在本地集群的Kibana控制台中,配置待迁移和迁移后的索引。

    1. 步骤四:验证数据迁移结果

    在远程集群中插入数据,在本地集群中,验证数据是否迁移成功。

    准备工作


    1. 准备远程和本地Elasticsearch集群。{#cmd-ps8-i4x-dwf}

    具体操作步骤请参见创建阿里云Elasticsearch实例。要求两个实例为相同版本(6.7.0及以上),可用区类型为单可用区,且在同一专有网络和虚拟交换机下。

    1. 参见登录Kibana控制台,在远程集群中创建待迁移的索引。

           PUT myindex
         {
           "settings": {
       "index.soft_deletes.retention.operations": 1024,
       "index.soft_deletes.enabled": true
           }
         }

    注意

    对于7.0及以下版本的Elasticsearch实例,在创建索引时,需要开启soft_deletes属性,否则会报错。
    如果您需要迁移已创建的索引,需要通过重建索引来开启soft_deletes属性。

    1. 关闭待迁移的索引的物理复制功能。

    对于6.7.0版本的阿里云Elasticsearch实例,系统会默认为新建索引开启物理复制功能。使用CCR功能时,需要先关闭物理复制功能。

    1. 关闭索引。

         POST myindex/_close
      
    2. 更新索引settings,关闭物理复制功能。

         POST myindex/_settings
         {
         "index.replication.type" : null
         }
      
    3. 打开索引。{#cmd-c4f-nw3-dk6}

         POST myindex/_open
      

    步骤一:配置实例网络互通


    参见配置实例网络互通,在远程集群中添加需要进行网络互通的本地集群。最终配置如下。
    在这里插入图片描述

    步骤二:添加远程集群


    1. 登录本地集群的Kibana控制台。

    具体操作步骤请参见登录Kibana控制台

    1. 在左侧导航栏,单击 Management
    2. Elasticsearch 区域中,单击 Remote Clusters
    3. 单击 Add a remote cluster
    4. Add remote cluster 页面中,输入远程集群信息。

       ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200730191817233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2Mzk2NTYz,size_16,color_FFFFFF,t_70)
      

    Name :远程集群的名称,不可重复。
    Seed nodes :需要配置为远程集群的主节点的IP地址:9300。远程集群的主节点的IP地址,可在远程集群的Kibana控制台中,使用GET /_cat/nodes?v命令获取。
    在这里插入图片描述

     **注意** 由于CCR功能是Kibana通过数据节点之间的TCP端口(9300),访问数据节点IP的形式来进行网络互通,因此不支持HTTP端口(9200)访问。
    
    1. 单击 Save

    保存后,系统会自动连接远程集群。连接成功后,显示 Connected
    在这里插入图片描述

    步骤三:配置跨集群复制


    1. 在本地集群Kibana控制台的 Management 页面,单击 Elasticsearch 区域中的 Cross Cluster Replication
    2. 单击 Create a follower index
    3. Add follower index 页面,配置跨集群复制信息。
      在这里插入图片描述

    | 参数 | 说明 |
    |--------------------|----------------------------------------------------------------------------------------------|
    | Remote cluster | 选择您在步骤二:添加远程集群中添加的集群。 |
    | Leader index | 待迁移的索引。本文使用在准备工作中创建的 myindex 索引。 |
    | Follower index | 迁移数据生成的索引。索引名称不可重复。 |

    1. 单击 Create

    创建成功后,索引的状态显示为 Active
    在这里插入图片描述

    步骤四:验证数据迁移结果


    1. 参见登录Kibana控制台,在远程集群中插入数据。

         POST myindex/_doc/
         {
           "name":"Jack",
           "age":40
         }
      
    2. 在本地集群中,验证数据是否迁移成功。

         GET myindex_follow/_search
      

    迁移成功后,返回如下结果。
    在这里插入图片描述

    从以上结果可以看到,远程集群的Leader索引(myindex)中的数据,已通过CCR功能复制到了本地集群的Follower索引(myindex_follow)中。

    1. 在远程集群中,重新插入一条数据,随即在本地集群中进行查看,验证增量数据是否实时同步。

         POST myindex/_doc/
         {
           "name":"Pony",
           "age":50
         }
      

    数据插入后,立即在本地集群中进行查看,结果如下。
    在这里插入图片描述

    从以上结果可以看到,通过CCR可以实现增量数据的实时同步。
    说明 您也可以通过CCR功能的API,进行跨集群复制相关操作,详情请参见Cross-cluster replication APIs

    ]]>
    基于阿里云数据湖分析服务和Apache Hudi构建云上实时数据湖 Fri, 20 Jun 2025 02:20:33 +0800 1. 什么是实时数据湖

    大数据时代数据格式的多样化,如结构化数据、半结构化数据、非结构化数据,传统数据仓库难以满足各类数据的存储,同时传统数仓已经难以满足上层应用如交互式分析、流式分析、ML等的多样化需求。而数仓T+1的数据延迟导致分析延迟较大,不利于企业及时洞察数据价值;同时随着云计算技术发展以及云上对象存储的廉价性,使得越来越多企业基于云来构建数据湖,而传统数据湖由于缺失ACID事务能力,导致其上构建的表不支持事务,同时也无法处理数据的更新删除,数据湖能力未得到进一步释放。为企业更快洞见数据价值和补齐ACID事务等能力,需要引入实时数据湖,以此作为大数据处理架构来对应上层应用各类分析需求。

    2. 大数据平台方案

    2.1 传统Hadoop方案

    大数据时代以Hadoop体系为首的大数据分析平台逐渐表现出优异性,而围绕Hadoop体系的生态圈也不断完善,Hadoop体系从根本上解决了传统数据仓库的瓶颈问题。
    undefined

    传统批处理带来的延迟较大,并且随数据规模增长,通常会遇到如下问题。

    • HDFS限制:许多依赖 HDFS 扩展其大数据基础架构的公司都面临着这个问题。根据设计,HDFS 受 NameNode 内存容量的限制,因此存储大量小文件会显着影响性能。当数据大小超过10PB这个问题开始出现,如果数据量达到50-100 PB就会成为问题。 幸运的是,有一些相对简单的解决方案可以将 HDFS 从几十PB扩展到几百PB,例如利用 ViewFS 和 HDFS NameNode Federation;或通过控制小文件的数量并将不同的数据移到单独的集群,这样我们能够减轻 HDFS 的瓶颈。
    • 快速更新:对于很多用例而言,需要尽可能地访问新数据,而传统数仓T+1的更新延迟太大,无法满足对数据实时性要求很高的场景,同时由于数据延迟太大,无法利于企业做出及时决策。
    • 支持在 Hadoop上 更新和删除:大数据下的分布式存储强调数据的只读性质,所以类似于Hive,HDFS这些存储方式都不支持update,HDFS的write操作也不支持并行,这些特性导致其具有一定的局限性,无法支持对现有数据的更新和删除操作。为应对平台数据规模增长,必须找到一种方法来解决 HDFS 文件系统中的这种限制,以便支持更新/删除操作。

    2.2 Lambda方案

    对于批处理而言,虽然数据质量高,但其延迟太大。考虑到数据延迟问题,业界还有一种比较流行的架构,Lambda架构,兼具低延迟与稳定。

    undefined

    Lambda架构中,一份数据会分别进入速度层进行流式处理生成实时的增量视图和进入批处理层进行批量处理生成稳定可靠的历史视图,在上层查询时会合并增量视图和历史视图形成完整视图返回,这样便兼顾了数据的低延迟,但同时可以看到该架构需要维护两份数据、两份结果存储和多个处理框架,加重了系统维护负担。

    能否兼顾系统的可运维性和数据低延迟以及规避上述在传统HDFS上方案的弊端来构建一个可伸缩的实时数据湖呢?答案是肯定的,可以基于阿里云DLA(Data Lake Analytics 数据湖分析) + Apache Hudi构建实时数据湖。

    3. 阿里云实时数据湖方案

    使用DLA + Hudi技术方案可轻松在阿里云OSS上构建实时可分析的数据湖。

    企业典型的数据链路如下。

    • 各类App数据采集到Kafka或其他MQ;
    • 对Kafka中数据使用Spark/Flink等引擎进行处理;
    • 将处理结果写出(DB、HDFS、OSS等);
    • 通过分析引擎(Presto/Hive/Spark)对结果分析生成报表等;

    现在DLA已经内置集成Hudi,与此同时利用DLA内置Spark开箱即用的能力,用户便可在DLA中快速构建Hudi数据湖,架构如下所示。

    undefined

    用户通过DLA SparkStreaming消费上游数据,然后以Hudi增量格式写入OSS并自动同步元数据至DLA Meta;当然对于用户自建Spark集群方式也可支持,也只需要将上游数据以Hudi格式写入OSS并自动关联至DLA Meta即可,接着便可以使用DLA-SQL进行在线交互式分析或使用DLA-Spark进行机器学习和离线分析。两种方案都极大降低了用户使用DLA的门槛,也体现了DLA极致的开放能力,基于DLA和Hudi构建实时数据湖总结有如下优势

    • 全链路数据延迟可达分钟级别,打造T + 0 数据湖;
    • 支持数据增量存储在OSS,支持Upsert/Delete,同时自动构建元数据管理;
    • 丰富的数据源,支持阿里云上超过95%数据源;
    • 支持全托管的SQL & Spark,免去集群运维;
    • 弹性Serverless SQL/Spark,满足交互式、批处理、机器学习多种工作负载;
    • 一份数据存储在OSS,通过DLA Meta增量管理,降低存储成本低;
    • 支持多租户及按照扫描量计费,能有效管理多分析师的查询需求和SQL使用量;

    下面简单介绍下什么是DLA和Apache Hudi。

    3.1. 什么是DLA

    阿里云数据湖分析Data Lake Analytics是阿里云数据库自研的核心产品,是新一代CloudNative分析平台;开放计算,支持MySQL协议,支持Presto、Spark引擎;主打低成本、Serverless无托管成本;统一元数据、可以让用户拥有统一的数据视图。目前在阿里云服务数千客户。
    更多详情可参考:https://www.aliyun.com/product/datalakeanalytics

    2.png

    DLA的Serverless能力免去了企业高昂的运维成本及应对数据波峰波谷扩缩容等繁琐步骤,按量计费,无持有成本。同时DLA没有单独存储用户数据,用户数据以开放的格式存储在OSS中,然后只需将元数据关联到DLA Meta后便可使用DLA SQL进行分析,或者通过DLA Spark进行复杂的ETL操作。

    3.2 什么是Apache Hudi

    Apache Hudi是一个支持插入、更新、删除的增量数据湖处理框架,可以用来管理分布式文件系统(如HDFS)/云上(OSS、S3)超大规模数据集。Hudi提供了如下关键特性

    • 可插拔索引机制支持快速Upsert/Delete
    • 支持增量拉取表变更以进行处理
    • 支持时间旅行,查看旧版本数据
    • 支持ACID,事务提交及回滚
    • 自动管理小文件以优化查询性能
    • 基于行存的快速写入,并支持异步压缩为列存便于分析
    • 用于进行审计跟踪的元数据时间轴

    更多详情可参考https://hudi.apache.org/

    4. Demo示例

    可参考实时数据湖快速入门 了解如何使用DLA和Hudi构建实时数据湖示例。

    5. 总结

    本篇文章首先介绍了什么是数据湖,以及常见的大数据解决方案,然后介绍了阿里云实时数据湖方案,使用DLA + Hudi方案快速构建近实时可分析数据湖,并列举方案优势,最后提供了简单Demo示例展示如何集成DLA和Hudi。

    感兴趣的小伙伴欢迎入钉钉群交流

    undefined

    ]]>
    DataWorks百问百答47:如何配置操作Python UDF函数? Fri, 20 Jun 2025 02:20:33 +0800 udf函数是什么?
    maxcompute自带了一些函数比如:max/min/sum 等,但是由于自带的函数数量有限且实现的功能有较大的局限性,通常不能满足业务的需要,这时用户可以自己定义udf来方便扩展。udf 函数可以直接应用于select 语句,对查询结构做格式化处理之后,然后再输出内容。


    注意点:
    1.Python UDF必须通过annotate指定函数签名。
    2.必须实现 evaluate 方法
    操作步骤:
    step1:
    创建python类型ud资源:
    案例如下:(实现两个bigint类型字段值相加)
    image.png
    from odps.udf import annotate
    @annotate ( "bigint,bigint->bigint" )
    class MyPlus ( object ):
    def evaluate ( self , arg0 , arg1 ):
    if None in ( arg0 , arg1 ):
    return None
    return arg0 + arg1

    step2:
    根据资源来配置创建函数:

    image.png
    注意点1:类名为资源文件名.class类名
    注意点2:资源列表中填写 所有 用到的文件(例如udf中用到的其他文本类型文件等),用英文逗号分隔

    step3:
    调用函数实现业务需求:
    image.png

    注意点1:调用语句中使用的是函数名

    DataWorks百问百答历史记录 请点击这里查看>>

    更多DataWorks技术和产品信息,欢迎加入【DataWorks钉钉交流群】

    ]]>
    SPA 的 SEO 方案对比、最终实践 Fri, 20 Jun 2025 02:20:33 +0800 原文链接:https://taskhub.work/article/73058307795021824
    已获作者授权转载

    前端开发技术日新月异,由于现代化构建、用户体验的需求,angular/vue/react 等框架已经成为开发标配,大部分应用都是 SPA,同时也带来了很多新问题:

    • SEO 不友好
    • 首屏渲染慢

    为了解决这些问题,开源社区有很多方案,本文主要对这些方案进行对比。

    一、客户端渲染(CSR)方案

    React开发的SPA就是一种CSR方案,如图所示,在到达浏览器之前的html页面是没有内容的,要等到浏览器执行相应异步请求获取数据填充后才显示界面。

    优点

    • SPA 的优点(用户体验较好)

    缺点

    • SEO不友好(爬虫如果没有执行js的能力,如百度,获取到的页面是空的,不利于网站推广)
    • 首屏加载慢(到达浏览器端后再加载数据,增加用户等待时间)

    二、服务端渲染 (SSR)方案


    基本原理: 在服务端起一个node应用,浏览器到来时,先拦截执行部分 js 异步请求,提前将数据填充到 html 页面中返回浏览器。这样爬虫抓取到的页面就是带数据的,有利于SEO

    需解决问题:

    1. 大部分应用开发时都有状态管理方案(Vuex, Redux),SPA 应用到达浏览器前状态都是空的,使用SSR后意味着需要在服务端提前填充数据到 store
    2. 需要拦截相应 hook(vue 的 created、react 的 componentDidMount),等待异步数据请求完成,确认渲染完成

    针对这些问题,社区也有相应框架可参考:

    框架 解决方案 Github star
    Vue Nuxt.js 28.4k
    React Nextjs 50.8k
    Angular - -

    不想使用框架,也可以自己修改react、vue 的 render 方法实现(改动工作量更大)

    优点

    • SEO 友好
    • 首屏渲染快(可在服务端缓存页面,请求到来直接给 html)

    缺点

    • 代码改动大、需要做特定SSR框架的改动(经过我们实践、原有SPA代码改动非常大)
    • 丢失了部分SPA体验
    • node 容易成为性能瓶颈

    三、构建时预渲染方案

    Solution Github Star
    prerender-spa-plugin 6k
    puppeteer 63.2k
    phantomjs 1.4k

    基本原理: 利用webpack 等构建工具,针对 SPA 应用开发后只有一个 index.html 文件入口问题,用上述预渲染中间件在前端项目构建时预先获取页面数据,生成多个页面,如 about、help 、contact 等页面,优化首屏渲染与部分页面SEO

    优点

    • 代码侵入性小

    缺点

    • 无法用于大量动态路径页面场景(生成的 html 页面数据大,而且页面数据会有更新。如 /article/123,文章页面)
    • 后台请求数据变动时前端应该同步更新版本

    四、服务端动态渲染(利用user-agent)

    回归到原始需求,为了提高用户体验我们用了SPA技术、为了SEO 我们用了 SSR、预渲染等技术。不同技术方案有一定差距,不能兼顾优点。但仔细想,需要这些技术优点的“用户”,其实时不一样的,SPA 针对的是浏览器普通用户、SSR 针对的是网页爬虫,如 googlebot、baiduspider 等,那为什么我们不能给不同“用户”不同的页面呢,服务端动态渲染就是这种方案。

    基本原理: 服务端对请求的 user-agent 进行判断,浏览器端直接给 SPA 页面,如果是爬虫,给经过动态渲染的 html 页面

    PS: 你可能会问,给了爬虫不同的页面,会不会被认为是网页作弊行为呢?

    Google 给了回复

    Dynamic rendering is not cloaking

    Googlebot generally doesn't consider dynamic rendering as cloaking. As long as your dynamic rendering produces similar content, Googlebot won't view dynamic rendering as cloaking.

    When you're setting up dynamic rendering, your site may produce error pages. Googlebot doesn't consider these error pages as cloaking and treats the error as any other error page.

    Using dynamic rendering to serve completely different content to users and crawlers can be considered cloaking. For example, a website that serves a page about cats to users and a page about dogs to crawlers can be considered cloaking.

    也就是说,如果我们没有刻意去作弊,而是使用动态渲染方案去解决SEO问题,爬虫经过对比网站内容,没有明显差异,不会认为这是作弊行为。

    优点

    • 兼顾 SPA优点同时解决SEO问题

    缺点

    • 需要服务端应用(但动态渲染只针对爬虫、不会成为性能瓶颈)

    总结: 经过前期其他方案的实践、优缺点权衡、最终我们选择了方案四的动态渲染作为 SPA 的 SEO 方案。

    实现细节

    上图为最终实现。(存在优化点:右边CDN整合、可以考虑使用Node替代nginx部分功能,简化架构)

    社区方案:

    方案 github star 描述
    puppeteer 63.2k 可用于动态渲染、前端测试、操作模拟。API丰富
    rendertron 4.9k 动态渲染
    prerender.io 5.6k 动态渲染

    选型使用 puppeteer 作为动态渲染方案。

    依赖:

    {
      "dependencies": {
        "bluebird": "^3.7.2",
        "express": "^4.17.1",
        "puppeteer": "^5.2.0",
        "redis": "^3.0.2",
        "request": "^2.88.2"
      }
    }

    代码参考Google 官方 Demo进行改造,下面是基础代码:

    server.js

    import express from 'express';
    import request from 'request';
    import ssr from './ssr.js';
    
    const app = express();
    
    const host = 'https://www.abc.com';
    
    app.get('*', async (req, res) => {
        const {html, ttRenderMs} = await ssr(`${host}${req.originalUrl}`);
        res.set('Server-Timing', `Prerender;dur=${ttRenderMs};desc="Headless render time (ms)"`);
        return res.status(200).send(html); // Serve prerendered page as response.
    });
    
    app.listen(8080, () => console.log('Server started. Press Ctrl + C to quit'));
    

    ssr.js

    import puppeteer from 'puppeteer';
    
    // In-memory cache of rendered pages.
    const RENDER_CACHE = new Map();
    
    async function ssr(url) {
        if (RENDER_CACHE.has(url)) {
            return {html: RENDER_CACHE.get(url), ttRenderMs: 0};
        }
        const start = Date.now();
    
        const browser = await puppeteer.launch({
            args: ['--no-sandbox', '--disable-setuid-sandbox']
        });
        const page = await browser.newPage();
        try {
            // networkidle0 waits for the network to be idle (no requests for 500ms).
            await page.goto(url, {waitUntil: 'networkidle0'});
            await page.waitForSelector('#root'); // ensure #posts exists in the DOM.
        } catch (err) {
            console.error(err);
            throw new Error('page.goto/waitForSelector timed out.');
        }
    
        const html = await page.content(); // serialized HTML of page DOM.
        await browser.close();
    
        const ttRenderMs = Date.now() - start;
        console.info(`Puppeteer rendered page: ${url} in: ${ttRenderMs}ms`);
    
        RENDER_CACHE.set(url, html); // cache rendered page.
    
        return {html, ttRenderMs};
    }
    
    export {ssr as default};

    Demo 代码存在以下问题:

    • 页面渲染后返回浏览器,有时会再次执行异步请求获取数据(重复请求)
    • 使用了 Map 做页面缓存,在node服务崩溃时会丢失全部缓存。没有超时限制,随着时间增长,内存消耗大(缓存机制)
    • 重复请求 React/Vue 静态文件,ssr 函数会当成一个页面进行渲染(错误渲染)

    下面对这些问题逐个击破

    重复请求:

    根本原因是React/Vue 代码生命周期函数重复执行。一般我们在created/componentDidMount hook 进行异步数据请求,这个hook在动态渲染的时候执行了一次,在HTML返回浏览器的时候,dom挂载又执行了一次,此问题在Google Support也有提及。可以通过小小改造前端代码,判断页面是否已被动态渲染再执行异步请求。可参考:

    componentDidMount() {
        const PRE_RENDERED = document.querySelector('#posts');
        if(!PRE_RENDERED) {
            // 异步请求
            // 插入含有 #posts id 的 dom 元素
        }
    }

    缓存机制

    针对 Map 缓存的问题,我们使用了Redis进行改造,增加超时机制,同时可以避免node崩溃缓存击穿问题

    redis/index.js

    import redis from 'redis';
    import bluebird from 'bluebird';
    
    bluebird.promisifyAll(redis);
    
    const host = 'www.abc.com';
    const port = 6379;
    const password = '123456';
    
    const client = redis.createClient({
        host,
        port,
        password,
        retry_strategy: function(options) {
            if (options.error && options.error.code === "ECONNREFUSED") {
                return new Error("The server refused the connection");
            }
            if (options.total_retry_time > 1000 * 60 * 60) {
                return new Error("Retry time exhausted");
            }
            if (options.attempt > 10) {
                return undefined;
            }
            return Math.min(options.attempt * 100, 3000);
        },
    });
    
    client.on("error", function(e) {
        console.error('dynamic-render redis error: ', e);
    });
    
    export default client;

    ssr.js

    import puppeteer from 'puppeteer';
    import redisClient from './redis/index.js';
    
    async function ssr(url) {
        const REDIS_KEY = `ssr:${url}`;
        const CACHE_TIME = 600; // 10 分钟缓存
        const CACHE_HTML = await redisClient.getAsync(REDIS_KEY);
    
        if (CACHE_HTML) {
            return { html: CACHE_HTML, ttRenderMs: 0 };
        }
        const start = Date.now();
    
        const browser = await puppeteer.launch({
            args: ['--no-sandbox', '--disable-setuid-sandbox']
        });
        const page = await browser.newPage();
        try {
            // networkidle0 waits for the network to be idle (no requests for 500ms).
            await page.goto(url, {waitUntil: 'networkidle0'});
            await page.waitForSelector('#root'); // ensure #posts exists in the DOM.
        } catch (err) {
            console.error(err);
            throw new Error('page.goto/waitForSelector timed out.');
        }
    
        const html = await page.content(); // serialized HTML of page DOM.
        await browser.close();
    
        const ttRenderMs = Date.now() - start;
        console.info(`Puppeteer rendered page: ${url} in: ${ttRenderMs}ms`);
    
        redisClient.set(REDIS_KEY, html, 'EX', CACHE_TIME); // cache rendered page.
        return {html, ttRenderMs};
    }
    
    export {ssr as default};

    错误渲染

    渲染后的页面回到浏览器后,有时执行操作会重新加载样式文件,请求路径类似:/static/1231234sdf.css,这些路径会被当做一个页面路径,而不是静态资源进行渲染,导致渲染错误。解决方式:增加 path 匹配拦截,资源文件直接向原域名请求

    import express from 'express';
    import request from 'request';
    import ssr from './ssr.js';
    
    const app = express();
    
    const host = 'https://www.abc.com';
    
    app.get('/static/*', async (req, res) => {
        request(`${host}${req.url}`).pipe(res);
    });
    
    app.get('/manifest.json', async (req, res) => {
        request(`${host}${req.url}`).pipe(res);
    });
    
    app.get('/favicon.ico', async (req, res) => {
        request(`${host}${req.url}`).pipe(res);
    });
    
    app.get('/logo*', async (req, res) => {
        request(`${host}${req.url}`).pipe(res);
    });
    
    app.get('*', async (req, res) => {
        const {html, ttRenderMs} = await ssr(`${host}${req.originalUrl}`);
        res.set('Server-Timing', `Prerender;dur=${ttRenderMs};desc="Headless render time (ms)"`);
        return res.status(200).send(html); // Serve prerendered page as response.
    });
    
    app.listen(8080, () => console.log('Server started. Press Ctrl + C to quit'));
    

    动态渲染相比SSR有几点明显好处:

    • 和 SSR 一致的 SEO 效果,通过 puppeteer 还可进一步定制 SEO 方案
    • node 应用负载压力小,只需应对爬虫请求,相当于只有爬虫来了页面才做SSR
    • 从整体架构上来说相当于一个插件,可随时插拔,无副作用
    • 不需要大量修改SPA代码(只在重复请求问题上用一个标志位去识别,当然也可以不管这个问题)

    (重复请求只在爬虫有js执行能力时才出现,一般再次请求数据也没问题)

    附录

    常见爬虫 user-agent

    主体 user-agent 用途
    Google googlebot 搜索引擎
    Google google-structured-data-testing-tool 测试工具
    Google Mediapartners-Google Adsense广告网页被访问后,爬虫就来访
    Microsoft bingbot 搜索引擎
    Linked linkedinbot 应用内搜索
    百度 baiduspider 搜索引擎
    奇虎 360 360Spider 搜索引擎
    搜狗 Sogou Spider 搜索引擎
    Yahoo Yahoo! Slurp China 搜索引擎
    Yahoo Yahoo! Slurp 搜索引擎
    Twitter twitterbot 应用内搜索
    Facebook facebookexternalhit 应用内搜索
    - rogerbot -
    - embedly -
    Quora quora link preview -
    - showyoubot -
    - outbrain -
    - pinterest -
    - slackbot -
    - vkShare -
    - W3C_Validator -

    模拟爬虫测试

    # 不带 user-agent 返回SPA页面,html 上无数据
    curl 你的网站全路径
    # 模拟爬虫、返回页面应该带有 title,body 等数据,方便 SEO
    curl -H 'User-agent:Googlebot' 你的网站全路径

    参考资料

    【1】构建时预渲染:网页首帧优化实践

    【2】Implement dynamic rendering

    【3】Google 抓取工具(用户代理)概览

    ]]>
    基于Docker部署的Jmeter分布式压测 Fri, 20 Jun 2025 02:20:33 +0800 为什么需要分布式压测

    在压测工作中我们经常遇见对一些关键接口需要压测到很高的QPS,这时候我们需要设置更多的线程去模拟虚拟用户去请求接口,假如我们需要模拟20000个用户,在单台机器很难模拟20000个用户,因为Jmeter是用Java语言开发,每创建一个线程,JVM默认会为每个线程分配1M的堆栈内存空间,这里只计算所需要的内存就需要20G的内存。一般我们的施压机器配置是4核8G或者8核16G的,此时我们需要多台机器共同完成施压请求。

    分布式压测一键部署

    分布式压测架构示意图如下图所示
    image.png

    Jmeter分布式测试环境中有两个角色:Master和Slaves

    1. Master节点:向参与的Slaves节点发送测试脚本,并聚合Agent节点的执行结果,部署一台
    2. Slaves节点:接收并执行Master节点发送过来的测试脚本,并将执行结果返回给Master,可部署多台

    部署前置条件,你的机器上已经安装了Docker

    下载Master节点:
    docker pull runcare/jmeter-master

    下载Slaves节点:
    docker pull runcare/jmeter-slave

    分布式压测使用

    1. 启动Slaves节点,这里假如我们启动三台机器
    docker run -it -d --name slave01 runcare/jmeter-slave
    docker run -it -d --name slave02 runcare/jmeter-slave
    docker run -it -d --name slave03 runcare/jmeter-slave

    image.png

    1. 准备一个测试脚本文件test.jmx
    2. 查看一下Slaves机器的IP地址
      docker inspect -f '{{ .Name }} => {{ .NetworkSettings.IPAddress }}' $(docker ps -q)
    3. Master机器发送脚本
    /Users/eleme/Downloads/jmeter-master 是你脚本test.jmx所在的目录
    result=`date +"%Y%m%d%H%M%S"` && docker run --rm -v /Users/eleme/Downloads/jmeter-master:/data a4789222b813 jmeter -n -t /data/test.jmx -l /data/$result.jtl -j /data/$result.log -e -o /data/$result -R 172.17.0.2,172.17.0.3,172.17.0.4

    或者

    docker run --rm -v $(pwd):/data 20cb9e02cfe8 jmeter -n -t /data/aggregation.jmx -l /data/result.jtl -j /data/result.log  -R 172.17.0.2,172.17.0.3

    image.png

    1. 生产的结果文件,日志文件和报表文件在脚本文件test.jmx同一目录下
      image.png
    2. 如果压测脚本中使用到了csv数据源文件,需要提前复制到Slaves的/data目录下

    注意事项

    1. Master和Slaves需要在同一网段,如果mac电脑Master使用安装在mac电脑中的Jmeter,Slaves使用Docker中的Slaves,需要在启动Slaves时将端口映射出来
      docker run -it -d -p 1099:1099 -p 60001:60001 runcare/jmeter-slave
    2. 执行Master发送脚本时也需要指定server.hostname和server.rmi.localport
    进入test.jmx所在目录
    result=`date +"%Y%m%d%H%M%S"` && jmeter -n -t test.jmx -l $result.jtl -j $result.log -e -o $result -Djava.rmi.server.hostname=30.208.47.45 -Dserver.rmi.localport=60002 -Dserver_port=1098

    Master和Slaves制作附件

    1. Master制作的Dockerfile
    # oracle jdk 1.8 备用
    #FROM runcare/debian-jre1.8
    
    # openjdk 1.8
    FROM runcare/openjdk-jre1.8
    
    # 更新版本1
    MAINTAINER runcare<larrygui@foxmail.com>
    
    ARG JMETER_VERSION="5.1.1"
    ENV JMETER_HOME /opt/apache-jmeter-$JMETER_VERSION
    ENV JMETER_DOWNLOAD_URL  https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
    ENV SSL_DISABLED true
    
    RUN mkdir -p /tmp/dependencies  
        && curl -L --silent $JMETER_DOWNLOAD_URL >  /tmp/dependencies/apache-jmeter-$JMETER_VERSION.tgz  
        && mkdir -p /opt  
        && tar -xzf /tmp/dependencies/apache-jmeter-$JMETER_VERSION.tgz -C /opt  
        && rm -rf /tmp/dependencies
    
    # TODO: plugins (later)
    # && unzip -oq "/tmp/dependencies/JMeterPlugins-*.zip" -d $JMETER_HOME
    
    # Set global PATH such that "jmeter" command is found
    ENV PATH $PATH:$JMETER_HOME/bin
    
    VOLUME ["/data"]
    
    WORKDIR    $JMETER_HOME
    
    RUN sed 's/#server.rmi.ssl.disable=false/server.rmi.ssl.disable=true/g' ./bin/jmeter.properties > ./bin/jmeter_temp.properties
    RUN mv ./bin/jmeter_temp.properties ./bin/jmeter.properties
    1. Slaves制作的Dockerfile
    # oracle jdk 1.8 备用
    #FROM runcare/debian-jre1.8
    
    # openjdk 1.8
    FROM runcare/openjdk-jre1.8
    
    # 更新版本1
    MAINTAINER runcare<larrygui@foxmail.com>
    
    ARG JMETER_VERSION="5.1.1"
    ENV JMETER_HOME /opt/apache-jmeter-$JMETER_VERSION
    ENV JMETER_DOWNLOAD_URL  https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
    ENV SSL_DISABLED true
    
    RUN mkdir -p /tmp/dependencies  
        && curl -L --silent $JMETER_DOWNLOAD_URL >  /tmp/dependencies/apache-jmeter-$JMETER_VERSION.tgz  
        && mkdir -p /opt  
        && tar -xzf /tmp/dependencies/apache-jmeter-$JMETER_VERSION.tgz -C /opt  
        && rm -rf /tmp/dependencies
    
    # TODO: plugins (later)
    # && unzip -oq "/tmp/dependencies/JMeterPlugins-*.zip" -d $JMETER_HOME
    
    # Set global PATH such that "jmeter" command is found
    ENV PATH $PATH:$JMETER_HOME/bin
    
    VOLUME ["/data"]
    
    WORKDIR    $JMETER_HOME
    
    EXPOSE 1099 60001
    
    ENTRYPOINT jmeter-server -Dserver.rmi.localport=60001 -Dserver_port=1099 
                -Jserver.rmi.ssl.disable=$SSL_DISABLED
    ]]>
    中国信通院:2020年云计算发展白皮书 Fri, 20 Jun 2025 02:20:33 +0800 前言:
    -更多关于数智化转型、数据中台内容请加入阿里云数据中台交流群—数智俱乐部 和关注官方微信公总号(文末扫描二维码或点此加入

    -阿里云数据中台官网 https://dp.alibaba.com/index

    (来源:199IT)

    中国信通院云计算与大数据研究所副所长栗蔚在会上正式发布并解读白皮书,总结出的2020年云计算发展六大关键词,以及六大关键词背后的重要趋势。

    白皮书指出:未来,云计算仍将迎来下一个黄金十年,进入普惠发展期。一是随着新基建的推进,云计算将加快应用落地进程,在互联网、政务、金融、交通、物流、教育等不同领域实现快速发展。二是全球数字经济背景下,云计算成为企业数字化转型的必然选择,企业上云进程将进一步加速。三是新冠肺炎疫情的出现,加速了远程办公、在线教育等SaaS服务落地,推动云计算产业快速发展。”

    关键词1:云原生

    随着市场持续增长,云技术也不断推陈出新,其中一个值得高度关注的趋势是——云原生采纳率持续攀升。栗蔚介绍,目前超四成的企业已经在使用容器技术,超过七成的私有云企业已经使用或计划使用微服务架构。

    关键词2:SaaS

    “疫情下,越来越多的企业接受SaaS的模式。”栗蔚表示,从业务上看,我国IaaS发展成熟,PaaS增长高速,SaaS潜力巨大。2019年,我国SaaS市场规模达到194亿元,与全球整体市场(1095亿美元)的成熟度差距明显,但是发展空间却十分巨大。尤其是受疫情的推动,预计未来市场将加速发展。

    关键词3:分布式云

    “随着云边协同的发展,在工业等多个领域,分布式云将成为主要模式。”栗蔚说,中国信通院的调研显示,超过50%的用户已经计划或者已经使用边缘云的模式,“中心云+边缘云”的分布式云的架构已经崭露头角。

    关键词4:原生云安全

    “近年来,一个新的理念诞生,即原生云安全。”栗蔚表示,中国信通院发布的《中国公有云发展调查报告(2020年)》显示,42.4%的企业在选择公有云服务商时会考虑服务安全性,安全是影响企业选择的重要因素。而随着云原生快速兴起,原生云安全也成为关注焦点。

    关键词5:数字化转型

    数字化转型已经成为经济社会发展的重要趋势。栗蔚指出,随着云计算技术、架构、安全等方面的推陈出新,云计算在数字化转型中扮演重要角色。调查显示,超过五成的企业使用云计算是为了降本增效,超四成的企业表示使用云计算提升了IT运行效率,IT运维工作量减少和安全性提升的占比分别为25.8%和24.2%。

    关键词6:新基建

    “随着利好政策不断加码,云计算已经成为新基建的重要组成部分。”栗蔚表示,无论是工业和信息化部发布的《中小企业数字化赋能专项行动方案》,国家发展改革委、中央网信办发布的《关于推进“上云用数赋智”行动培育新经济发展实施方案》,还是国家发展改革委对新基建概念的解读,都表明——云计算已经成为新基建的重要组成部分。

    最后,《白皮书》指出,2020年又是一个新十年的开端,无论是如火如荼的“新基建”、稳步推进的企业数字化转型,还是突如其来的疫情,都将云计算推向了一个新高度。未来十年,云计算将进入全新发展阶段,具体表现为以下六大趋势:

    趋势1:云技术从粗放向精细转型

    趋势2:云需求从IaaS向SaaS上移

    趋势3:云架构从中心向边缘延伸

    趋势4:云安全从外部向原生转变

    趋势5:云应用从互联网向行业生产渗透

    趋势6:云定位既是基础资源也是基建操作系统

    详细报告可点击查看:链接文字
    或者扫码钉钉群下载

    ]]>
    理解Rust的Result/Option/unwrap/? Fri, 20 Jun 2025 02:20:33 +0800 我在学习Rust时,注意到有4个概念经常放到一起讨论:Result、Option、unwapr和?操作符。本文记录了我对这4个Rust概念的思考,这个思考过程帮助我理解并学会了如何写出更地道的Rust代码。

    区块链开发教程链接:以太坊 | 比特币 | EOS | Tendermint | Hyperledger Fabric | Omni/USDT | Ripple

    1、Option - 可空变量

    虽然Rust中有null的概念,但是使用null并不是Rust中常见的模式。假设我们要写一个函数,输入一种手机操作系统的名称,这个函数就会返回其应用商店的名称。如果传入字符串iOS,该函数将返回App Store;如果传入字符串android,那么该函数将返回Play Store。任何其他的输入都被视为无效。

    在大多数开发语言中,我们可以选择返回null或字符串invalid来表示无效的结果,不过这不是Rust的用法。

    地道的Rust代码应该让该函数返回一个Option。Option或更确切的说Option<T>是一个泛型,可以是Some<T>None(为了便于阅读,后续文章中将省略类型参数T)。Rust将SomeNone称为变体(Variant) —— 这一概念在其他语言中并不存在,因此我也不
    去定义到底什么是变体了。

    在我们的示例中,正常情况下函数将返回包裹在Some变体中的字符串常量App Store或Play Store。而在非正常情况下,函数将返回None。

    fn find_store(mobile_os: &str) -> Option<&str> {
        match mobile_os {
            "iOS" => Some("App Store"),
            "android" => Some("Play Store"),
            _ => None
        }
    }

    要使用find_store(),我们可以用如下方式调用:

    fn main() {
        println!("{}", match find_store("windows") {
            Some(s) => s,
            None => "Not a valid mobile OS"
        });
    }

    完整的代码如下:

    fn find_store(mobile_os: &str) -> Option<&str> {
        match mobile_os {
            "iOS" => Some("App Store"),
            "android" => Some("Play Store"),
            _ => None
        }
    }
    
    fn main() {
        println!("{}", match find_store("windows") {
            Some(s) => s,
            None => "Not a valid mobile OS"
        });
    }

    2、Result - 包含错误信息的结果

    Result,或者更确切地说Result<T,E>,是和Rust中的Option相关的概念,它是一个加强版本的Option。

    Result可能有以下结果之一:

    • Ok(T):结果为成员T
    • Err(E):结果为故障成员E

    与之前我们看到Option可以包含Some或None不同,Result中包含了错误相关信息,这是Option中所没有的。

    让我们看一个函数实例,它返回一个Result。该函数摘自用于解析JSON字符串的serde_json库,其签名为:

    pub fn from_str<'a, T>(s: &'a str) -> Result<T, Error> 
    where
        T: Deserialize<'a>, 

    假设我们要解析如下的字符串:

    let json_string = r#"
        {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    目标是解析为Rust的一个person结构对象:

    #[derive(Serialize, Deserialize)]
    struct Person {
        name: String,
        age: u8,
        phones: Vec<String>,
    }

    解析过程的Rust代码如下:

    let p:Person = match serde_json::from_str(json_string) {
        Ok(p) => p,
        Err(e) => ... //we will discuss what goes here next 
    };

    正常情况下可以得到期望的结果。不过假设在输入的json_string中有一个笔误,这导致程序运行时将执行Err分支。

    当碰到Err时,我们可以采取两个动作:

    • panic!
    • 返回Err

    3、unwrap - 故障时执行panic!

    在上面的示例中,假设我们期望panic!:

    let p: Person = match serde_json::from_str(data) {
            Ok(p) => p,
            Err(e) => panic!("cannot parse JSON {:?}, e"), //panic
        }

    当碰到Err时,上面的代码panic!就会崩掉整个程序,也许这不是你期望的。我们可以修改为:

    let p:Person = serde_json::from_str(data).unwrap();

    如果我们可以确定输入的json_string始终会是可解析的,那么使用unwrap没有问题。但是如果会出现Err,那么程序就会崩溃,无法从故障中恢复。在开发过程中,当我们更关心程序的主流程时,unwrap也可以作为快速
    原型使用。

    因此unwrap隐含了panic!。虽然与更显式的版本没有差异,但是危险在于其隐含特性,因为有时这并不是你真正期望的行为。

    无论如何,如果我们需要调用panic!,代码如下:

    use serde::{Deserialize, Serialize};
    use serde_json::Result;
    
    #[derive(Serialize, Deserialize)]
    struct Person {
        name: String,
        age: u8,
        phones: Vec<String>,
    }
    
    fn typed_example() -> Result<()> {
        //age2 is error on purpose
        let data = r#"
            {
                "name": "John Doe",
                "age2": 43,
                "phones": [
                    "+44 1234567",
                    "+44 2345678"
                ]
            }"#;
    
        let p:Person = serde_json::from_str(data).unwrap();
    
        println!("Please call {} at the number {}", p.name, p.phones[0]);
    
        Ok(())
    }
    
    fn main() {
        match typed_example() {
            Ok(_) => println!("program ran ok"),
            Err(_) => println!("program ran with error"),
        }
    }

    4、? - 故障时返回Err对象

    当碰到Err时,我们不一定要panic!,也可以返回Err。不是每个Err都是不可恢复的,因此有时并不需要panic!。下面的代码返回Err:

    let p: Person = match serde_json::from_str(data) {
            Ok(p) => p,
            Err(e) => return Err(e.into()),
    };

    ?操作符提供了一个更简洁的方法来替换上面的代码:

    let p:Person = serde_json::from_str(data)?;

    这时完整的Rust程序代码如下:

    use serde::{Deserialize, Serialize};
    use serde_json::Result;
    
    #[derive(Serialize, Deserialize)]
    struct Person {
        name: String,
        age: u8,
        phones: Vec<String>,
    }
    
    fn typed_example() -> Result<()> {
        //age2 is error on purpose
        let data = r#"
            {
                "name": "John Doe",
                "age2": 43,
                "phones": [
                    "+44 1234567",
                    "+44 2345678"
                ]
            }"#;
    
        let p: Person = serde_json::from_str(data)?;
    
        println!("Please call {} at the number {}", p.name, p.phones[0]);
    
        Ok(())
    }
    
    fn main() {
        match typed_example() {
            Ok(_) => println!("program ran ok"),
            Err(e) => println!("program ran with error {:?}", e),
        }
    }

    5、使用unwrap和?解包Option

    就像我们可以使用unwarp和?来处理Result,我们也可以使用unwrap和?来处理Option。

    如果我们unwrap的Option的值是None,那么程序就会panic!。示例如下:

    fn next_birthday(current_age: Option<u8>) -> Option<String> {
        // If `current_age` is `None`, this returns `None`.
        // If `current_age` is `Some`, the inner `u8` gets assigned to `next_age` after 1 is added to it
        let next_age: u8 = current_age?;
        Some(format!("Next year I will be {}", next_age + 1))
    }
    
    fn main() {
      let s = next_birthday(None);
      match s {
          Some(a) => println!("{:#?}", a),
          None => println!("No next birthday")
      }
    }

    原文链接:Rust学习 - Result/Option/unwrap/? — 汇智网

    ]]>
    SaaS 模式云数据仓库 MaxCompute 数据安全最佳实践 Fri, 20 Jun 2025 02:20:33 +0800 什么是 MaxCompute?

    MaxCompute 是一款云原生、高效能的SaaS模式企业级数据仓库服务,被广泛用于构建现代化企业数据平台,开展BI分析、数据化运营、画像及推荐、智能预测等应用场景。

    MaxCompute 构建在阿里云大规模计算、存储资源之上,以Serverless架构提供全托管的在线数据仓库服务,消除了传统数据平台在资源扩展性和弹性方面的限制,并最小化用户的运维投入。

    MaxCompute支持多种经典计算模型(批处理、机器学习、交互式分析等)和完善的企业管理功能,借助MaxCompute,用户可轻松集成和管理企业数据资产,简化数据平台架构,加速价值实现。

    MaxCompute 企业级安全能力升级

    MaxCompute 近期对产品的安全能力进行了全面升级。 发布的安全能力有:

    · 细粒度授权
    · 数据加密 (BYOK)
    · 数据脱敏(数据保护伞)
    · 持续备份恢复
    · 跨地域的容灾备份
    · 实时审计日志

    MaxCompute 安全体系

    对于一个企业级的大数据平台,要应对的安全风险,有三个层次(如图-1):
    1.基础安全与可信平台,保障数据中心的物理安全与网络安全,主要包括数据中心保障设施、数据中心安全管控、数据中心的网络安全等几个维度的建设。
    2.大数据平台的系统安全,主要由访问控制、安全隔离、风控审计、以及数据保护等子系统构成,为上层安全应用或工具提供平台能力基础。
    3.数据应用的安全,为用户提供工具化的数据安全产品,优化用户体验,帮助用户更好应对各类数据风险。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-1:大数据平台安全体系)

    近期的MaxCompute安全能力升级,主要新功能覆盖了访问控制、风控审计、以及数据保护几个子系统,如图-1中“大数据平台安全”层中,黄色高亮字体部分。本文中,我们将针对几类主要的数据风险(如图-2),介绍这些数据风险应对的最佳实践。在最佳实践中,将会穿插介绍何时使用、为什么使用、如何使用这些新功能。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-2:主要数据风险)

    如何应对数据误用

    数据误用是由于非故意的、过失性动作导致的,防止误用一般指防止数据被不经意间错误使用。应对数据误用的风险,防止数据误用,核心的一点,就是了解数据,能够回答这些问题:我有什么数据,这些数据在哪里,这些数据是怎么来的、又被如何使用,等一系列问题。

    1. MaxCompute 提供基础元数据信息

    MaxCompute 可以帮助用户很好的回答这些问题。 MaxCompute 平台构建了统一的元数据管理,基于统一元数据和完备的平台日志,向用户提供元数据和相关日志数据。 用户可以基于 MaxCompute 的 Information Schema,构建自己的数据管理应用。

    2. 使用数据地图作为数据管理工具

    大多数用户更希望通过现有的数据管理应用或服务,来了解自己的数据:“DataWorks-数据地图”就是这样的应用。 数据总览、数据明细等信息能帮助用户了解自己有哪些数据以及数据的明细信息;产出和使用信息、血缘信息,则能帮助用户了解数据的来龙去脉,帮助用户正确、合理的使用数据。 使正确的数据,被正确的使用在正确的场景下。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-3:使用数据地图了解数据)

    如何应对数据滥用

    数据滥用指的是对数据的使用超出了其预先约定的场景或目的,数据滥用一般是靠故意的、带有目的性的动作完成的。而应对数据滥用,最主要的应对是对数据使用做最小化授权,严格限制数据的被访问、使用的范围。权限管理的最佳实践,推荐图-5中的4大过程:

    • 数据分级管理:基于 MaxCompute 的 LabelSecurity 对数据做分类分级管理。
    • 授权审批流程:基于 MaxCompute 的 列级别权限管控能力, 对数据的访问使用需求,做最小化授权。
    • 定期审计:对权限的申请、审批、使用情况进行分析,做到事前有审批,事后有审计。
    • 及时清理:及时清理过期权限,减少数据风险。

    可以依托 MaxCompute 的细粒度权限体系,使用 Dataworks 等白屏化工具,来实现最小化授权的最佳实践,应对数据滥用的风险。

    1. (New) MaxCompute 细粒度权限体系提供精细化的权限管理能力

    MaxCompute支持不同的授权机制来完成对用户或角色的授权,包括:
    • 自主访问控制机制 (DAC, Discretionary Access Control): ACL
    • 强制访问控制机制 (MAC, Mandatory Access Control):LabelSecurity(标签安全策略)
    • 基于角色的访问控制机制 (RBAC, Role based Access Control): 角色管理

    不论是哪种访问控制机制,授权鉴权过程中的三个要素是相同的:Action,Object,以及Subject,如下图。

    在此次的MaxCompute 安全能力发布中,也包括权限模型的升级,支持更细粒度的授权鉴权,提供精细化的权限管理能力。 主要新功能有:

    • ACL 支持列级别权限管理,增加Condition支持,增加授权有效期支持;
    • 细粒度 Package 内资源权限管控,对 Package 内的资源可以支持到列级别的权限管控;
    • 增加独立的 Download 数据下载权限管理,对更高风险的数据批量下载场景做独立权限管控;
    • 管理类权限支持分级授权管理,内置 super administrator 角色来分解project owner 管理负担;
    • 完善 RBAC,LabelSecurity 增加对 Role 的支持;
    • 增强对应用端的权限管理能力。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-4:MaxCompute 细粒度权限体系)

    (橙色高亮字体为此次细粒度权限能力发布)

    2.使用安全中心进行白屏化权限管理

    MaxCompute 的细粒度权限体系提供了的实现最小化授权的平台能力,结合一些白屏化工具,如“DataWorks-安全中心”,则可以提供更好的用户体验,让用户更方便的实现权限管理。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-5:使用安全中心做白屏化权限管理)

    安全中心提供便捷的权限管控功能和可视化的申请、审批流程,也可以进行权限的审计和管理:

    • 权限自助申请:选择所需权限的数据表/字段,在线上快速发起申请。
    • 权限审计及交还:管理员可以查看数据权限的对应人员,进行审计管理,用户也可以主动交还不再需要的权限。
    • 权限审批管理:在线审批授权模式,提供可视化、流程化的管理授权机制,并可以对审批流程进行事后追溯。

    如何应对数据泄露

    1.数据生命周期

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-6:数据生命周期)

    数据泄露可能发生在数据生命周期的多个阶段,如数据传输、数据存储、数据处理、数据交换等阶段。因此,我们将结合数据生命周期的不同阶段来介绍应对数据泄露的最佳实践。

    首先,数据从不同的渠道被采集,经过各类传输通道,进入大数据平台。 在大数据平台中,经过计算后落盘存储;数据也会通过数据分享机制,在不同的租户、业务之间流转;经过一定周期后,一些数据也会被删除销毁。经过处理后的数据,则会通过不同的传输通道,被其他数据应用、或者用户消费。 (如图-7)。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-7:大数据平台中的数据生命周期)

    2.(New) 应对数据存储过程中的数据泄露风险 - 使用数据加密(存储加密)功能

    我们首先看一下如何应对数据存储过程中的数据泄露风险:如磁盘数据被直接访问,磁盘被获取,等风险。应对此类情况的措施,是对磁盘数据进行加密,这样即使数据被恶意获取,加密后的数据也无法被解读使用。
    此次安全能力升级中, MaxCompute 发布了存储加密功能,支持用户数据的落盘加密:

    • MaxCompute接入秘钥管理系统KMS以保障秘钥的安全性,支持服务秘钥和用户自选秘钥(BYOK)。
    • 用户可以在创建MaxCompute项目时,配置选择打开存储加密功能(存量用户可以通过工单申请开通)。
    • 支持加密算法:AES256,国密算法,等。
    • .数据加密后对用户使用保持透明,各种类型的任务不需额外改变。

    3.应对数据数据处理过程中的数据泄露风险 - MaxCompute 安全隔离能力

    在数据处理过程中,应对数据泄露的风险则主要在于大数据平台的安全隔离能力。
    MaxCompute 提供独立的隔离环境用于执行数据处理应用,可以支持完整的UDF种类,支持 Java和Python UDF, 还支持执行如Spark、Flink、Tensorflow 等开源三方计算引擎,提供了多元化的数据处理能力。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-8:MaxCompute 安全隔离能力)

    4.应对数据交换(共享)过程中的数据泄露风险 - MaxCompute数据隔离与权限体系

    在数据交换、或者说数据共享过程中,则需要完善的数据隔离能力与权限管理体系来保障数据安全、防范数据泄露风险。MaxCompute 提供不同层级和维度上的数据隔离与权限管理机制,以支持多层次的数据保护和数据共享场景。

    • 多租户的数据安全隔离:MaxCompute 支持多租户的使用场景,针对不同的用户数据进行数据存储隔离,用户数据被离散存储在分布式文件系统中,满足多用户协同、共享、和安全的需要,做到真正的多租户资源隔离。

    • 租户内的业务(Project)数据隔离与共享:同一租户下,不同业务(Project)之间的数据隔离、以及一定程度上的数据共享是非常常见的场景。基于ProjectProtection 保护机制可以实现 Project之间的数据隔离与保护,二Package则能让用户更方便同时也更安全的实现跨Project的数据和资源分享。如前文“MaxCompute 细粒度权限体系提供精细化的权限管理能力”介绍,此次安全能力升级增加了对Package的数据和资源做细粒度的权限管理,增强了Package的数据共享和保护能力。

    • (New) 应用端数据访问控制:通过对访问MaxCompute的的应用增加签名机制,增强了对应用端访问控制的管理能力。 例如,只允许特定的应用可以进行授权语句的操作,以避免用户通过接口或不合规的应用进行非法数据授权操作。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-9:MaxCompute 数据隔离能力)

    5.(New) 数据生命周期中的敏感数据保护

    应对数据泄露风险中的一个重要主题是敏感数据保护,前文所述在存储、处理、和交换过程中的风险应对实践,对敏感数据保护同样适用。 此外,还有一些针对敏感数据保护这一特定场景的最佳实践:

    • 数据分类分级:使用 MaxCompute 的 LabelSecurity 功能,对数据做安全性的分类分级,对不同类别不同安全等级的数据访问和使用,进行精细化的权限管理。

    • (New) 数据脱敏:基于安全行业的脱敏实现或应用,结合 MaxCompute 的平台 UDF 能力,实现不同客户端数据输出时的敏感数据脱敏。脱敏实现也可以与数据分类分级结合使用,对不同分类分级的数据做不同的脱敏实现。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-10:敏感数据保护)

    (New)用数据保护伞作为敏感数据保护工具

    数据保护伞,是基于 MaxCompute 平台的数据分类分级能力和接入脱敏应用能力、构建的敏感数据保护工具。用户可以使用数据保护伞对敏感数据进行标识,选择脱敏算法,在数据屏显输出时进行脱敏。
    更多产品说明和使用介绍,详见《数据保护伞》用户文档。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-11:敏感数据保护工具 - 数据保护伞)

    如何应对数据丢失

    除了恶意的数据泄露、数据滥用等风险,数据开发过程中的各种误操作,偶发的设备或机房故障,甚或是罕见的灾害意外情况,都能造成数据丢失的后果。 应对数据丢失风险的最佳实践,主要有备份恢复,以及容灾能力。

    1.(New) MaxCompute 备份与恢复

    数据开发过程中,避免不了会有误操作删除数据(如Drop/Truncate Table)后需要恢复,或使用“insert into”、“insertoverwrite”语法执行后发现数据有问题需要恢复之前版本。

    MaxCompute 近期发布了持续的备份与恢复能力,系统会自动备份数据的历史版本(例如被删除或修改前的数据)并保留一定时间,您可以对保留周期内的数据进行快速恢复,避免因误操作丢失数据。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-12:MaxCompute 持续备份与恢复能力)

    2.(New) MaxCompute 异地容灾

    MaxCompute 的异地容灾能力,更好的提供了在机房故障或意外灾害等极端场景下的数据安全保障。
    在为 MaxCompute 项目指定备份位置到备份集群后,MaxCompute 自动实现主集群与备份集群的数据复制,达到主集群与被集群数据的一致,实现异地数据容灾。当发生故障,MaxCompute 项目从主集群切换到备份集群后,使用备份集群的计算资源访问备份集群的数据,完成服务的切换和恢复。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-13:MaxCompute 异地容灾)

    善用审计,应对各类数据风险

    至此,我们已经介绍了在数据开发和使用过程中,应对各类数据风险的实践。我们把非常重要的、适用于各类数据风险应对的一个实践,放在最后介绍:善用日志,构建预警和审计能力。

    MaxCompute 提供了完善的历史数据和实时日志:

    • Information Schema:提供了项目元数据及使用历史数据等信息。PRIVILEGES 和 HISTORY 类的视图,可以帮助用户对数据权限使用、任务执行等维度做分析审计。

    • (New) 实时审计日志功能:MaxCompute 完整记录了用户的各项操作行为,如DDL、授权、任务执行等各类事件,满足实时审计、问题回溯分析等需求。

    基于 Information Schema 和 实时审计日志,用户可以构建自己的数据风控和审计体系。Information Schema 去年就已上线,下文将主要介绍新发布的实时审计日志。

    当然,并不是所有的用户都计划自己构建风控和审计工具,这种情况下,可以直接使用 Dataworks 中的已有产品,进行风控和审计。优点是无需用户二次开发、开箱即用,缺点则是定制的弹性较小。

    1.(New) 实时审计日志

    敏感数据是否被过度使用?数据访问权限是否被过度授予?是否有异常如计划外高频的数据访问?在数据安全保障中,管理者常常需要回答这些问题。 MaxCompute 审计日志可以帮助回答这些问题。

    MaxCompute完整地记录用户的各项操作行为,并通过阿里云ActionTrail服务将用户行为日志实时推送给ActionTrail。用户可以在ActionTrail中查看和检索用户行为日志,同时通过ActrionTrail将日志投递到日志服务项目或指定的OSS Bucket中,满足实时审计、问题回溯分析等需求。

    ActionTrail针对作业(Instance)、表(Table)、函数(Function)、资源(Resource)、用户(User)、角色(Role)和授权(Privilege)等事件的多种操作行为进行审计,详细功能说明和使用介绍,详见《审计日志》用户文档。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-14:MaxCompute 审计日志)


    2.使用 DataWorks 中的审计工具

    用户也可以使用 Dataworks 的已有产品,进行数据安全的风控和审计:

    • 在前文中介绍的安全中心,可以提供权限的审计。
    • 数据保护伞也提供了风控和审计能力,如图-15。
    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-15:使用数据保护伞做风控和审计)

    小结

    小结的同时呼应开篇,我们再次来看企业级大数据平台三个层次的数据安全保障体系。 这次我们把 MaxCompute 的安全能力按数据生命周期的6个阶段来重新组织,如图-16。帮助大家更好理解,在不同的数据生命阶段,应该采用哪些实践来实施安全保障。图-16中的黄色高亮部分,则标识了此次 MaxCompute 安全能力升级中的新功能。

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    (图-16:基于大数据平台构建数据什么周期的安全保障)

    作为 SaaS 模式下的云数据仓库,MaxCompute 具备领先的安全能力,也通过了国际、欧洲、国内的多项安全合规认证,如国际主流认证ISO系列、SOC1/2/3、PCI,欧洲主流认证C5,国内主流认证安全等级保护2.0,等。 阿里云整体的安全合规认证,详见《阿里云信任中心-合规认证》页面。 欢迎大家使用 MaxCompute,构建企业级的大数据安全。

    发布会传送门

    查看产品详情

    ]]>
    分布式接口限流实现 Fri, 20 Jun 2025 02:20:33 +0800 @[toc]
    ## 为什么要接口限流

    • 在我们项目开发过程中,有些接口是暴露在用户的常用中,包括一些高危接口,如 (支付,开发票,订单),这些接口 都是高危接口,且被用户经常使用,在高并发的情况下,io阻塞,不可避免的出现重复提交,或者点击频繁的操作,所以我们就要加入限流,避免用户多次点击,减少我们接口的压力,把整数据不会重复,接口压力减小

    为什么要做分布式

    • 在我们做项目负载均衡的时候, 分布式,微服务架构的时候,不可避免的多个节点,这个时候我们就要考虑会被随机分配到各个节点,如果 我们使用 令牌桶 或者 漏斗桶 算法到话,存到 本地,各个节点不会共享,所以
      我们要考虑模块,节点间的共享

    实现方式

    1. 算法实现(无分布式,单体架构,单节点)

    1. 自定义注解
    package com.yxl.annotation;
    
    import org.springframework.core.annotation.AliasFor;
    
    import java.lang.annotation.*;
    import java.util.concurrent.TimeUnit;
    
    /**
     * <p>
     * 限流注解,
     * </p>
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RateLimiter {
    
        int NOT_LIMITED = 0;
    
        /**
         * qps
         */
        @AliasFor("qps") double value() default NOT_LIMITED;
    
        /**
         * qps
         */
        @AliasFor("value") double qps() default NOT_LIMITED;
    
        /**
         * 超时时长
         */
        int timeout() default 0;
    
        /**
         * 超时时间单位
         */
        TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    
    }
    1. AOP实现切面 + 令牌桶算法实现
    package com.yxl.aspect;
    
    import com.yxl.annotation.RateLimiter;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    
    /**
     * <p>
     * 限流切面
     * </p>
     *
     * @author yxl
     * @date Created in 2019/9/12 14:27
     */
    @Slf4j
    @Aspect
    @Component
    public class RateLimiterAspect {
        private static final ConcurrentMap<String, com.google.common.util.concurrent.RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
    
        @Pointcut("@annotation(com.yxl.annotation.RateLimiter)")
        public void rateLimit() {
    
        }
    
        @Around("rateLimit()")
        public Object pointcut(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解
            RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);
            if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) {
                double qps = rateLimiter.qps();
                if (RATE_LIMITER_CACHE.get(method.getName()) == null) {
                    // 初始化 QPS
                    RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps));
                }
    
                log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());
                // 尝试获取令牌
                if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) {
                    throw new RuntimeException("手速太快了,慢点儿吧~");
                }
            }
            return point.proceed();
        }
    }
    

    使用方式

    在这里插入图片描述

    查看结果(这里使用了自定义异常)
    在这里插入图片描述

    2. 分布式实现

    package com.yxzapp.annotation;
    
    import org.springframework.core.annotation.AliasFor;
    
    import java.lang.annotation.*;
    import java.util.concurrent.TimeUnit;
    
    /**
     * <p>
     * 限流注解,
     * </p>
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RateLimiter {
    
        int NOT_LIMITED = 0;
    
        /**
         * 类名
         * @return
         */
        String className() default "";
    
        /**
         * qps
         */
        @AliasFor("qps") double value() default NOT_LIMITED;
    
        /**
         * qps
         */
        @AliasFor("value") double qps() default NOT_LIMITED;
    
        /**
         * 限流时间
         */
        int timeout() default 0;
    
        /**
         * 超时时间单位
         */
        TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    
    }
    

    使用 AOP + redis 实现

    package com.yxzapp.aspect;
    
    
    import com.yxzapp.annotation.RateLimiter;
    import com.yxzapp.commons.constant.MessageConstant;
    import com.yxzapp.exception.BizException;
    import com.yxzapp.modules.sys.entity.SysUser;
    import com.yxzapp.utils.RedisUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.SecurityUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    
    /**
     * <p>
     * 限流切面
     * </p>
     *
     * @author yxl
     * @date  2020/6/19
     */
    @Slf4j
    @Aspect
    @Component
    public class RateLimiterAspect {
    
        @Autowired
        private RedisUtils redisUtils;
    
        @Pointcut("@annotation(com.yxzapp.annotation.RateLimiter)")
        public void rateLimit() {
    
        }
    
        @Around("rateLimit()")
        public Object pointcut(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            Class aClass = signature.getClass();
    
            // 获取方法上的@RateLimiter注解
            RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);
        
            if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) {
                //获取qps
                double qps = rateLimiter.qps();
                
                String key = "RateLimiter:" rateLimiter.className() + +':'+ method.getName();
                if(!redisUtils.hasKey(key)){
                    redisUtils.setMillisecond(key,rateLimiter.qps(),rateLimiter.timeout());
                }else if(redisUtils.get(key) != null) {
                    throw new BizException(MessageConstant.MSG_STATUS,"手速太快了,慢点儿吧~");
                }
    
                log.debug("【{}】的QPS设置为: {}", key, redisUtils.get(key));
    
            }
            return point.proceed();
        }
    }

    使用方式

    在这里插入图片描述
    查看结果 (这里使用了自定义异常)
    在这里插入图片描述

    ]]>
    详细讲解!RabbitMQ防止数据丢失 Fri, 20 Jun 2025 02:20:33 +0800 思维导图

    在这里插入图片描述

    一、分析数据丢失的原因

    分析RabbitMQ消息丢失的情况,不妨先看看一条消息从生产者发送到消费者消费的过程:

    可以看出,一条消息整个过程要经历两次的网络传输:从生产者发送到RabbitMQ服务器,从RabbitMQ服务器发送到消费者

    在消费者未消费前存储在队列(Queue)中

    所以可以知道,有三个场景下是会发生消息丢失的:

    • 存储在队列中,如果队列没有对消息持久化,RabbitMQ服务器宕机重启会丢失数据。
    • 生产者发送消息到RabbitMQ服务器过程中,RabbitMQ服务器如果宕机停止服务,消息会丢失。
    • 消费者从RabbitMQ服务器获取队列中存储的数据消费,但是消费者程序出错或者宕机而没有正确消费,导致数据丢失。

    针对以上三种场景,RabbitMQ提供了三种解决的方式,分别是消息持久化,confirm机制,ACK事务机制。

    二、消息持久化

    RabbitMQ是支持消息持久化的,消息持久化需要设置:Exchange为持久化和Queue持久化,这样当消息发送到RabbitMQ服务器时,消息就会持久化。

    首先看Exchange交换机的类图:

    看这个类图其实是要说明上一篇文章介绍的四种交换机都是AbstractExchange抽象类的子类,所以根据java的特性,创建子类的实例会先调用父类的构造器,父类也就是AbstractExchange的构造器是怎么样的呢?

    从上面的注释可以看到durable参数表示是否持久化。默认是持久化(true)。创建持久化的Exchange可以这样写:

        @Bean
        public DirectExchange rabbitmqDemoDirectExchange() {
            //Direct交换机
            return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
        }

    接着是Queue队列,我们先看看Queue的构造器是怎么样的:

    也是通过durable参数设置是否持久化,默认是true。所以创建时可以不指定:

        @Bean
        public Queue fanoutExchangeQueueA() {
            //只需要指定名称,默认是持久化的
            return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A);
        }

    这就完成了消息持久化的设置,接下来启动项目,发送几条消息,我们可以看到:


    怎么证明是已经持久化了呢,实际上可以找到对应的文件:
    在这里插入图片描述
    找到对应磁盘中的目录:

    消息持久化可以防止消息在RabbitMQ Server中不会因为宕机重启而丢失

    三、消息确认机制

    3.1 confirm机制

    在生产者发送到RabbitMQ Server时有可能因为网络问题导致投递失败,从而丢失数据。我们可以使用confirm模式防止数据丢失。工作流程是怎么样的呢,看以下图解:
    在这里插入图片描述
    从上图中可以看到是通过两个回调函数confirm()、returnedMessage()进行通知。

    一条消息从生产者发送到RabbitMQ,首先会发送到Exchange,对应回调函数confirm()。第二步从Exchange路由分配到Queue中,对应回调函数则是returnedMessage()

    代码怎么实现呢,请看演示:

    首先在application.yml配置文件中加上如下配置:

    spring:
      rabbitmq:
        publisher-confirms: true
    #    publisher-returns: true
        template:
          mandatory: true
    # publisher-confirms:设置为true时。当消息投递到Exchange后,会回调confirm()方法进行通知生产者
    # publisher-returns:设置为true时。当消息匹配到Queue并且失败时,会通过回调returnedMessage()方法返回消息
    # spring.rabbitmq.template.mandatory: 设置为true时。指定消息在没有被队列接收时会通过回调returnedMessage()方法退回。

    有个小细节,publisher-returns和mandatory如果都设置的话,优先级是以mandatory优先。可以看源码:
    在这里插入图片描述
    接着我们需要定义回调方法:

    @Component
    public class RabbitmqConfirmCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
        private Logger logger = LoggerFactory.getLogger(RabbitmqConfirmCallback.class);
    
        /**
         * 监听消息是否到达Exchange
         *
         * @param correlationData 包含消息的唯一标识的对象
         * @param ack             true 标识 ack,false 标识 nack
         * @param cause           nack 投递失败的原因
         */
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (ack) {
                logger.info("消息投递成功~消息Id:{}", correlationData.getId());
            } else {
                logger.error("消息投递失败,Id:{},错误提示:{}", correlationData.getId(), cause);
            }
        }
    
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            logger.info("消息没有路由到队列,获得返回的消息");
            Map map = byteToObject(message.getBody(), Map.class);
            logger.info("message body: {}", map == null ? "" : map.toString());
            logger.info("replyCode: {}", replyCode);
            logger.info("replyText: {}", replyText);
            logger.info("exchange: {}", exchange);
            logger.info("routingKey: {}", exchange);
            logger.info("------------> end <------------");
        }
    
        @SuppressWarnings("unchecked")
        private <T> T byteToObject(byte[] bytes, Class<T> clazz) {
            T t;
            try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                 ObjectInputStream ois = new ObjectInputStream(bis)) {
                t = (T) ois.readObject();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
            return t;
        }
    }

    我这里就简单地打印回调方法返回的消息,在实际项目中,可以把返回的消息存储到日志表中,使用定时任务进行进一步的处理。

    我这里是使用RabbitTemplate进行发送,所以在Service层的RabbitTemplate需要设置一下:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        @Resource
        private RabbitmqConfirmCallback rabbitmqConfirmCallback;
    
        @Resource
        private RabbitTemplate rabbitTemplate;
    
        @PostConstruct
        public void init() {
            //指定 ConfirmCallback
            rabbitTemplate.setConfirmCallback(rabbitmqConfirmCallback);
            //指定 ReturnCallback
            rabbitTemplate.setReturnCallback(rabbitmqConfirmCallback);
        }
        
        @Override
        public String sendMsg(String msg) throws Exception {
            Map<String, Object> message = getMessage(msg);
            try {
                CorrelationData correlationData = (CorrelationData) message.remove("correlationData");
                rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, message, correlationData);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
        
        private Map<String, Object> getMessage(String msg) {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            CorrelationData correlationData = new CorrelationData(msgId);
            String sendTime = sdf.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            map.put("correlationData", correlationData);
            return map;
        }
    }

    大功告成!接下来我们进行测试,发送一条消息,我们可以控制台:
    在这里插入图片描述
    假设发送一条信息没有路由匹配到队列,可以看到如下信息:
    在这里插入图片描述
    这就是confirm模式。它的作用是为了保障生产者投递消息到RabbitMQ不会出现消息丢失

    3.2 事务机制(ACK)

    最开始的那张图已经讲过,消费者从队列中获取到消息后,会直接确认签收,假设消费者宕机或者程序出现异常,数据没有正常消费,这种情况就会出现数据丢失

    所以关键在于把自动签收改成手动签收,正常消费则返回确认签收,如果出现异常,则返回拒绝签收重回队列。
    在这里插入图片描述
    代码怎么实现呢,请看演示:

    首先在消费者的application.yml文件中设置事务提交为manual手动模式:

    spring:
      rabbitmq:
        listener:
          simple:
            acknowledge-mode: manual # 手动ack模式
            concurrency: 1 # 最少消费者数量
            max-concurrency: 10 # 最大消费者数量

    然后编写消费者的监听器:

    @Component
    public class RabbitDemoConsumer {
    
        enum Action {
            //处理成功
            SUCCESS,
            //可以重试的错误,消息重回队列
            RETRY,
            //无需重试的错误,拒绝消息,并从队列中删除
            REJECT
        }
    
        @RabbitHandler
        @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))
        public void process(String msg, Message message, Channel channel) {
            long tag = message.getMessageProperties().getDeliveryTag();
            Action action = Action.SUCCESS;
            try {
                System.out.println("消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:" + msg);
                if ("bad".equals(msg)) {
                    throw new IllegalArgumentException("测试:抛出可重回队列的异常");
                }
                if ("error".equals(msg)) {
                    throw new Exception("测试:抛出无需重回队列的异常");
                }
            } catch (IllegalArgumentException e1) {
                e1.printStackTrace();
                //根据异常的类型判断,设置action是可重试的,还是无需重试的
                action = Action.RETRY;
            } catch (Exception e2) {
                //打印异常
                e2.printStackTrace();
                //根据异常的类型判断,设置action是可重试的,还是无需重试的
                action = Action.REJECT;
            } finally {
                try {
                    if (action == Action.SUCCESS) {
                        //multiple 表示是否批量处理。true表示批量ack处理小于tag的所有消息。false则处理当前消息
                        channel.basicAck(tag, false);
                    } else if (action == Action.RETRY) {
                        //Nack,拒绝策略,消息重回队列
                        channel.basicNack(tag, false, true);
                    } else {
                        //Nack,拒绝策略,并且从队列中删除
                        channel.basicNack(tag, false, false);
                    }
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    解释一下上面的代码,如果没有异常,则手动确认回复RabbitMQ服务端basicAck(消费成功)。

    如果抛出某些可以重回队列的异常,我们就回复basicNack并且设置重回队列。

    如果是抛出不可重回队列的异常,就回复basicNack并且设置从RabbitMQ的队列中删除。

    接下来进行测试,发送一条普通的消息"hello":
    在这里插入图片描述
    解释一下ack返回的三个方法的意思。

    ①成功确认

    void basicAck(long deliveryTag, boolean multiple) throws IOException;

    消费者成功处理后调用此方法对消息进行确认。

    • deliveryTag:该消息的index
    • multiple:是否批量.。true:将一次性ack所有小于deliveryTag的消息。

    ②失败确认

    void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
    • deliveryTag:该消息的index。
    • multiple:是否批量。true:将一次性拒绝所有小于deliveryTag的消息。
    • requeue:被拒绝的是否重新入队列。

    ③失败确认

    void basicReject(long deliveryTag, boolean requeue) throws IOException;
    • deliveryTag:该消息的index。
    • requeue:被拒绝的是否重新入队列。

    basicNack()和basicReject()的区别在于:basicNack()可以批量拒绝,basicReject()一次只能拒接一条消息

    四、遇到的坑

    4.1 启用nack机制后,导致的死循环

    上面的代码我故意写了一个bug。测试发送一条"bad",然后会抛出重回队列的异常。这就有个问题:重回队列后消费者又消费,消费抛出异常又重回队列,就造成了死循环。
    在这里插入图片描述
    那怎么避免这种情况呢?

    既然nack会造成死循环的话,我提供的一个思路是不使用basicNack(),把抛出异常的消息落库到一张表中,记录抛出的异常,消息体,消息Id。通过定时任务去处理

    如果你有什么好的解决方案,也可以留言讨论~

    4.2 double ack

    有的时候比较粗心,不小心开启了自动Ack模式,又手动回复了Ack。那就会报这个错误:

    消费者RabbitDemoConsumer从RabbitMQ服务端消费消息:java技术爱好者
    2020-08-02 22:52:42.148 ERROR 4880 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)
    2020-08-02 22:52:43.102  INFO 4880 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer@f4a3a8d: tags=[{amq.ctag-8MJeQ7el_PNbVJxGOOw7Rw=rabbitmq.demo.topic}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,5), conn: Proxy@782a1679 Shared Rabbit Connection: SimpleConnection@67c5b175 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 56938], acknowledgeMode=AUTO local queue size=0

    出现这个错误,可以检查一下yml文件是否添加了以下配置:

    spring:
      rabbitmq:
        listener:
          simple:
            acknowledge-mode: manual
            concurrency: 1
            max-concurrency: 10

    如果上面这个配置已经添加了,还是报错,有可能你使用@Configuration配置了SimpleRabbitListenerContainerFactory,根据SpringBoot的特性,代码优于配置,代码的配置覆盖了yml的配置,并且忘记设置手动manual模式

    @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory);
            //设置手动ack模式
            factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            return factory;
        }

    如果你还是有报错,那可能是写错地方了,写在生产者的项目了。以上的配置应该配置在消费者的项目。因为ack模式是针对消费者而言的。我就是写错了,写在生产者,折腾了几个小时,泪目~

    4.3 性能问题

    其实手动ACK相对于自动ACK肯定是会慢很多,我在网上查了一些资料,性能相差大概有10倍。所以一般在实际应用中不太建议开手动ACK模式。不过也不是绝对不可以开,具体情况具体分析,看并发量,还有数据的重要性等等。

    所以在实际项目中还需要权衡一下并发量和数据的重要性,再决定具体的方案

    4.4 启用手动ack模式,如果没有及时回复,会造成队列异常

    如果开启了手动ACK模式,但是由于代码有bug的原因,没有回复RabbitMQ服务端,那么这条消息就会放到Unacked状态的消息堆里,只有等到消费者的连接断开才会转到Ready消息。如果消费者一直没有断开连接,那Unacked的消息就会越来越多,占用内存就越来越大,最后就会出现异常。

    这个问题,我没法用我的电脑演示,我的电脑太卡了。

    五、总结

    通过上面的学习后,总结了RabbitMQ防止数据丢失有三种方式:

    • 消息持久化
    • 生产者消息确认机制(confirm模式)
    • 消费者消息确认模式(ack模式)

    上面所有例子的代码都上传github了:

    https://github.com/yehongzhi/mall

    如果你觉得这篇文章对你有用,点个赞吧~

    你的点赞是我创作的最大动力~

    想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!
    在这里插入图片描述

    能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

    ]]>
    CentOS 6.x 搭建:Headless Chrome + ChromeDriver + Selenium基于浏览器的爬虫环境 Fri, 20 Jun 2025 02:20:33 +0800 【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107890747

    Chrome官方网站已经说的很清楚,不再支持6.x的CentOS,至少7以上。 可是很多时候我们使用的服务器版本并不能随便升级,即便已经很难受了,但是还得继续使用低版本,装起来那是真叫一个费劲,还好就是费劲一些,最终还是可以装成功的。

    什么是 Headless Chrome

    Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有 Chrome 支持的特性运行你的程序。相比于现代浏览器,Headless Chrome 更加方便测试 web 应用,获得网站的截图,做爬虫抓取信息等。相比于出道较早的 PhantomJS,SlimerJS 等,Headless Chrome 则更加贴近浏览器环境。

    CentOS版本

    lsb_release -a

    image.png

    最新版本Google-Chrome安装

    CentOS/RedHat 7以上安装google-chrome可以完全参考https://intoli.com/blog/installing-google-chrome-on-centos/ (6及以下版本不适用)。

    指定yum源

    服务器应该指定合适yum源,避免找不到某些依赖的尴尬。
    修改 /etc/yum.repos.d/CentOS-Base.repo,可以使用阿里的yum repo:

    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo
    yum clean all
    安装google-chrome

    根据https://intoli.com/blog/installing-google-chrome-on-centos/ ,执行命令:

    curl https://intoli.com/install-google-chrome.sh | bash

    脚本会自动检测当前版本安装chrome所缺失的依赖包并下载。
    image.png

    检查是否还缺乏依赖:

    ldd /opt/google/chrome/chrome | grep "not found"

    返回为空,说明CentOS下chrome依赖问题基本解决。

    运行chrome

    执行

    google-chrome-stable --no-sandbox --headless --disable-gpu --screenshot https://www.suning.com/。

    访问成功,在当前目录会生成截图screenshot.png,如果报错

    [0100/000000.311368:ERROR:broker_posix.cc(43)] Invalid node channel message

    则需要安装依赖包:

    yum install  
     ipa-gothic-fonts 
     xorg-x11-fonts-100dpi 
     xorg-x11-fonts-75dpi 
     xorg-x11-utils 
     xorg-x11-fonts-cyrillic 
     xorg-x11-fonts-Type1 
     xorg-x11-fonts-misc -y 

    最新版本Chromedriver安装

    当前的chrome版本是 google-chrome-stable-72.0.3626.109-1.x86_64,chromedrive的官网是https://sites.google.com/a/chromium.org/chromedriver/downloads
    image.png
    下载地址https://chromedriver.storage.googleapis.com/2.46/chromedriver_linux64.zip
    或者选择taobao镜像下载http://npm.taobao.org/mirrors/chromedriver/
    镜像下载地址http://npm.taobao.org/mirrors/chromedriver/2.46/chromedriver_linux64.zip

    解压后部署到/opt/drivers目录下,尝试运行:

    ./chromedriver 
    Starting ChromeDriver 72.0.3626.7 (efcef9a3ecda02b2132af215116a03852d08b9cb) on port 9515
    Only local connections are allowed.
    [1550143530.011][SEVERE]: CreatePlatformSocket() returned an error, errno=0: Address family not supported by protocol (97)

    另外要修改/etc/hosts,绑定127.0.0.1 localhost,否则,Java Selenium运行时chromedriver可能因为找不到localhost报超时异常

    安装selenium

    • 安装 Python 并配置好环境变量
      shell输入:python -V 出现对应版本号即安装成功!
    • 安装 pip

      python默认自带 pip 在安装目录的scripts目录下,自行配置至环境变量即可,配置好后shell输入:`pip -V` 出现对应版本号即安装成功!
    • 安装 selenium

      shell输入:`pip install selenium` 提示:Successfully installed selenium-即安装成功!
    python
    >>>from selenium import webdriver
    >>>driver = webdriver.Chrome()
    >>>driver.get('https://www.baidu.com')

    已经可以了,正常写python脚本即可。

    搭建环境时遇到的问题

    1、/lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./chromedriver)

    #查看系统版本
    cat /etc/redhat-release 
    #查看glibc支持的版本
    strings /lib64/libc.so.6 |grep GLIBC_
    
    wget http://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.gz 
    wget http://ftp.gnu.org/gnu/glibc/glibc-ports-2.14.tar.gz 
    tar -xvf  glibc-2.14.tar.gz 
    tar -xvf  glibc-ports-2.14.tar.gz
    mv glibc-ports-2.14 glibc-2.14/ports
    mkdir glibc-2.14/build
    cd glibc-2.14/build 
    ../configure  --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin
    make
    make install 

    安装编译过程中需要注意三点:

    • 要将glibc-ports解压到glibc目录下
    • 不能在glibc当前目录下运行configure
    • 加上优化开关,export CFLAGS="-g -O2 -march=i486",否则会出现错误
    • 在make install过程中可能会出现 nss_test1加载不了的情况, 此时可以将加载libnss_test1.so.2的地方注释掉,用grep "nss_test1" . -nr命令在 /glibc目录下查找一下,加载的地方也不多(这只是一个测试nss的静态库,可以不要)

    2、/lib64/libc.so.6: version `GLIBC_2.16' not found (required by ./chromedriver)

    wget http://ftp.gnu.org/gnu/glibc/glibc-2.16.0.tar.gz 
    wget http://ftp.gnu.org/gnu/glibc/glibc-ports-2.16.0.tar.gz 
    tar -xvf  glibc-2.16.0.tar.gz 
    tar -xvf  glibc-ports-2.16.0.tar.gz
    mv glibc-ports-2.16.0 glibc-2.16.0/ports
    mkdir glibc-2.16.0/build
    cd glibc-2.16.0/build 
    ../configure  --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin
    make
    make install 

    报错

    Unmatched ( in regex; marked by  HERE in m/$( <-- HERE if $(abi-64-ld-soname),$(abi-64-ld-soname),ld/ at scripts/test-installation.pl line

    可以参考解决办法https://sourceware.org/bugzilla/attachment.cgi?id=6616&action=diff

    • glibc-2.16.0/Makefile
    ifeq (,$(install_root))
          CC="$(CC)" $(PERL) scripts/test-installation.pl $(common-objpfx)
    endif

    改成

    ifeq (,$(install_root))
         LD_SO=$(ld.so-version) CC="$(CC)" $(PERL) scripts/test-installation.pl $(common-objpfx)
    endif
    • glibc-2.16.0/scripts/test-installation.pl
    sub usage {
        print "Usage: test-installation [soversions.mk]n";

    上面添加

    if ($ENV{LD_SO}) {
      $LD_SO = $ENV{LD_SO};
    } else {
      $LD_SO= "";
    }
    
    sub usage {
        print "Usage: test-installation [soversions.mk]n";

    } else {
      if (/^ld.so/) {
         ($ld_so_name, $ld_so_version)= /=(.*).so.(.*)$/;

    上面添加

    } elsif ($LD_SO ne "") {
        ($ld_so_name, $ld_so_version) = split ('.so.', $LD_SO);
    } else {
      if (/^ld.so/) {
         ($ld_so_name, $ld_so_version)= /=(.*).so.(.*)$/;

    欢迎关注 “后端老鸟” 公众号,接下来会发一系列的专题文章,包括Java、Python、Linux、SpringBoot、SpringCloud、Dubbo、算法、技术团队的管理等,还有各种脑图和学习资料,NFC技术、搜索技术、爬虫技术、推荐技术、音视频互动直播等,只要有时间我就会整理分享,敬请期待,现成的笔记、脑图和学习资料如果大家有需求也可以公众号留言提前获取。由于本人在所有团队中基本都处于攻坚和探路的角色,搞过的东西多,遇到的坑多,解决的问题也很多,欢迎大家加公众号进群一起交流学习。

    【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107890747

    image

    ]]>
    kafka_架构模型 Fri, 20 Jun 2025 02:20:33 +0800 Kafka架构模型

    p1014.png

    Kafka消费速度快:

    1. 页缓存:找个磁盘当内存;
    2. kafka采用顺序读写,比固态磁盘快

    p1015.png

    1. 如果消费速度太慢,更改topic的分区个数,就会有很多线程来消费。

    flume与kafka的整合

    flume监控文件夹,有新文件就搜集起来到kafka队列中

    1. source:spoolDir Source
    2. channel:memory channel
    3. sink:数据到kafka里面

    副本默认2

    注:仅作笔记。

    ]]>
    SpringBoot2 整合Ehcache组件,轻量级缓存管理 Fri, 20 Jun 2025 02:20:33 +0800 本文源码:GitHub·点这里 || GitEE·点这里

    一、Ehcache缓存简介

    1、基础简介

    EhCache是一个纯Java的进程内缓存框架,具有快速、上手简单等特点,是Hibernate中默认的缓存提供方。

    2、Hibernate缓存

    Hibernate三级缓存机制简介:

    一级缓存:基于Session级别分配一块缓存空间,缓存访问的对象信息。Session关闭后会自动清除缓存。

    二级缓存:是SessionFactory对象缓存,可以被创建出的多个 Session 对象共享,二级缓存默认是关闭的,如果要使用需要手动开启,并且依赖EhCache组件。

    三级缓存:查询缓存,配置开启该缓存的情况下,重复使用一个sql查询某个范围内的数据,会进行缓存。

    3、EhCache缓存特点

    • 快速,简单,并且提供多种缓存策略;
    • 缓存数据有两级:内存和磁盘,无需担心容量问题;
    • 缓存数据会在虚拟机重启的过程中写入磁盘;
    • 可以通过RMI、可插入API等方式进行分布式缓存;
    • 具有缓存和缓存管理器的侦听接口;
    • 支持多缓存管理器实例,以及一个实例的多个缓存区域;
    • 提供Hibernate的缓存实现;

    4、对比Redis缓存

    Ehcache:直接在Jvm虚拟机中缓存,速度快,效率高,不适合处理大规模缓存数据,在分布式环境下,缓存数据共享操作复杂;

    Redis:作为独立的缓存中间件,在分布式缓存系统中非常好用,缓存数据共享,有效支撑大量数据缓存,支持哨兵模式,或者集群模式的高可用成熟方案;

    二、集成SpringBoot框架

    1、核心依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>

    2、加载配置

    基础配置

    spring:
      cache:
        ehcache:
          config: classpath:ehcache.xml

    启动类注解

    @EnableCaching
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class,args) ;
        }
    }

    3、配置详解

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    
        <!-- 操作系统缓存的临时目录,内存满后写入该目录 -->
        <diskStore path="java.io.tmpdir"/>
    
        <defaultCache
                maxElementsInMemory="1000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </defaultCache>
    
        <cache name="userEntity"
               maxElementsInMemory="1000"
               eternal="false"
               timeToIdleSeconds="120"
               timeToLiveSeconds="120"
               maxElementsOnDisk="10000000"
               diskExpiryThreadIntervalSeconds="120"
               memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </cache>
    </ehcache>

    配置参数说明

    maxElementsOnDisk:磁盘缓存中最多可以存放的元素数量;

    eternal:缓存中对象是否永久有效;

    timeToIdleSeconds:当eternal=false时使用,缓存数据有效期(单位:秒),时间段内没有访问该元素,将被清除;

    timeToLiveSeconds:缓存数据的存活时间;

    maxElementsInMemory:内存中最多可以存放的元素数量,overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中,若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素;

    diskExpiryThreadIntervalSeconds:磁盘缓存的清理线程运行间隔;

    memoryStoreEvictionPolicy:缓存释放策略,LRU会优先清理最少使用的缓存;

    localTempSwap:持久化策略,当堆内存或者非堆内存里面的元素已经满了的时候,将其中的元素临时的存放在磁盘上,重启后就会消失;

    三、注解用法

    @Service
    public class CacheService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CacheService.class);
    
        @Resource
        private UserMapper userMapper ;
    
        @Cacheable(value="userEntity")  // 在缓存有效期内,首次查询才访问数据库
        public UserEntity getById (Integer id){
            // 通过日志,标识方法是否执行
            LOGGER.info("getById..."+id);
            return userMapper.selectById(id) ;
        }
    
        @CacheEvict(value="userEntity",key = "#id") //该ID数据更新,清空该ID缓存
        public void updateUser(Integer id) {
            UserEntity user = new UserEntity() ;
            user.setId(id);
            user.setUserName("myCache");
            userMapper.updateById(user);
        }
    }

    @Cacheable:注解标记在一个方法上,也可以标记在一个类上,标记在一个方法上表示该方法支持缓存,该方法被调用后将其返回值缓存起来,下次同样的请求参数执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。

    @CacheEvict:注解标记在需要清除缓存元素的方法或类上的,当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作,并且可以按照指定属性清除。

    四、源代码地址

    GitHub·地址
    https://github.com/cicadasmile/middle-ware-parent
    GitEE·地址
    https://gitee.com/cicadasmile/middle-ware-parent
    ]]>
    全新出击!《Java开发手册(嵩山版)》解读手册升级下载 Fri, 20 Jun 2025 02:20:33 +0800 《Java开发手册(嵩山版)》解读版升级下载

    随着《Java开发手册(嵩山版)》的发布,解读再升级!灵魂13问随新版JAVA开发手册重磅回归,一线大厂怎么用JAVA?千万阅读量技术博主15问为你全面剖析。

    作者介绍
    Hollis,一个对Coding有着独特追求的人,现任阿里巴巴技术专家,个人技术博主,技术文章全网阅读量数千万,《程序员的三门课》联合作者。

    免费下载
    《〈Java开发手册(嵩山版)〉灵魂15问》

    或者复制该链接到浏览器完成下载或分享:https://developer.aliyun.com/topic/download?id=811

    java 灵魂15问.jpg

    精彩导读

    image.png

    一、为什么禁止使用Apache Beanutils进行属性的copy?
    市面上有很多类似的属性拷贝工具类,比较常用的有
    1、Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer
    那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢?
    image.png
    接下来就聚焦于对比这几个类库的性能问题来分析。>>点击查看详情

    二、为什么要求日期格式化时必须有使用y表示年,而不能用Y?
    在Java中进行日期处理大家一定都不陌生,我们经常会需要在代码中进行日期的转换、日期的格式化等操作。

    而一般我们进行日期格式化的时候都会使用SimpleDateFormat工具,之前我们有一篇文章介绍过SimpleDateFormat的线程安全问题,这一篇文章再来介绍一个和SimpleDateFormat有关,很容易被忽视,而一旦忽视可能导致大故障的问题。>>点击查看详情

    三、《 Java 开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事?
    手册中有一条规约引起了作者的关注,那就是手册中提到在三目运算符使用过程中,需要注意自动拆箱导致的NullPointerException(后文简称:NPE)问题:
    image.png
    具体是怎样的呢?>>点击查看详情

    四、为什么建议初始化HashMap的容量大小?
    我们之前提到过,《Java 开发手册》中建议我们设置 HashMap 的初始化容量。
    image.png
    那么,为什么要这么建议?>>点击查看详情

    五、Java开发手册建议创建HashMap时设置初始化容量, 但是多少合适呢?
    HashMap 有扩容机制,就是当达到扩容条件时会进行扩容。HashMap 的扩容条件就是当 HashMap 中的元素个数(size)超过临界值(threshold)时就会自动扩容。在 HashMap 中,threshold = loadFactor * capacity

    所以,如果我们没有设置初始容量大小,随着元素的不断增加,HashMap 会发生多次扩容,而 HashMap 中的扩容机制决定了每次扩容都需要重建 hash 表,是非常影响性能的。>>点击查看创建HashMap时设置初始化容量多少合适

    六、为什么禁止使用Executors创建线程池?
    为什么说可以通过Executors静态工厂构建线程池,但一般不建议这样使用。
    本节我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?>>点击查看详情

    七、为什么要求谨慎使用ArrayList中的subList方法?
    集合是Java开发日常开发中经常会使用到的。

    关于集合类,《Java开发手册》中其实还有另外一个规定:
    image.png
    本节就来分析一下为什么会有如此建议?其背后的原理是什么?>>点击查看详情

    八、为什么不建议在for循环中使用“+”进行字符串拼接?
    使用+拼接字符串,其实只是Java提供的一个语法糖,那么他的内部原理到底是如何实现的。>>点击查看详情

    语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

    九、为什么禁止在for each循环里进行元素的remove/add操作?
    在Java开发手册中,有这样一条规定:
    image.png
    本节就来深入分析一下该规定背后的思考。>>点击查看详情

    十、为什么禁止工程师直接使用日志系统(Log4j、Log back) 中的API?
    作为Java程序员,我想很多人都知道日志对于一个程序的重要性,尤其是Web应用。很多时候,日志可能是我们了解应用程序如何执行的唯一方式。

    所以,日志在Java Web应用中至关重要,但是,很多人却以为日志输出只是一件简单的事情,所以会经常忽略和日志相关的问题。>>点击查看详情

    十一、为什么禁止把SimpleDateFormat定义成static变量?
    在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。

    最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。本节就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。>>点击查看详情

    十二、为什么禁止开发人员使用is Success作为变量名?
    在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。

    关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本节就来围绕这个简单分析一下,到底该如何定一个布尔类型的成员变量。>>点击查看详情

    十三、为什么禁止开发人员修改serialVersionUID字段的值?
    关于serialVersionUID 。这个字段到底有什么用?如果不设置会怎么样?为什么《Java开发手册》中有以下规定:
    image.png
    本节带你一探究竟。>>点击查看详情

    十四、为什么建议开发者谨慎使用继承?
    对于很多开发者来说,继承肯定都是不陌生的。但是,继承一定适合所有的场景吗?毫无忌讳的使用继承来做代码扩展真的好吗?
    为什么《Java开发手册》中有一条规定:谨慎使用继承的方式进行扩展,优先使用组合的方式实现。>>点击查看详情

    十五、为什么禁止使用count(列名) 或count(常量) 来替代count(*)?
    除了COUNT(id)和COUNT(*)以外,还可以使用COUNT(常量)(如COUNT(1))来统计行数,那么这三条SQL语句有什么区别呢?到底哪种效率更高呢?为什么《Java开发手册》中强制要求不让使用 COUNT(列名)或 COUNT(常量)来替代 COUNT(*)呢?
    image.png
    本节就这些问题带来解答。>>点击查看详情


    藏经阁系列电子书

    阿里云开发者社区——藏经阁系列电子书,汇聚了一线大厂的技术沉淀精华,爆款不断。
    点击链接获取海量免费电子书:https://developer.aliyun.com/topic/ebook

    开发者藏经阁.jpg

    ]]>
    ​Spring事务的传播行为案例分析 Fri, 20 Jun 2025 02:20:33 +0800

    网上关于Spring事务传播性以及隔离型的文章漫天盖地,还有不负责任的直接复制名词意思,文章虽然很多却是看的云里雾里,我们今天将给出案例分别和大家一起学习。

    1、spring给出经常面试的考点Spring事务的4个特性含义---这个很容易理解

    2、spring事务传播特性的定义以及案例分析 

    一、事务的特性ACID

    这四个英文单词拼写我一直记不住,求记忆方法

    • 原子性(Atomicity):事务是一系列原子操作,要么全部成功,要么全部失败。
    • 一致性(Consistency):一旦完成(不管是成功还是失败),确保它所在的一系列业务状态保持一致,状态都是成功,或者都是失败,不能一部分成功一部分失败。
    • 隔离性(Isolation):不同事务同时进行某项业务,处理相同的数据时候,需要保证事务之间相互独立,互相之间数据不影响。
    • 持久性(Durability):一旦事务完成,无论发生什么系统性错误,事务执行后的数据都被持久化了,不会因为重启或其他操作对数据进行更改。

    二、spring事务传播特性的定义以及案例分析 

    我们先给出定义再分别进行简单的代码分析

    给出百度图片,请大家参考,首先生命力如果想在工程中运用事务spring 的xml必须开启事务,以下这些特性一般都是在xml属性中进行配置。

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    类似这种的配置一定要有,否则万事具备只欠东风,这个是DB事务有JTA和JPA以及Herbernate等,这里就不展开说明,可自行百度。

    三、案例解析事务传播7大行为

    1、无事务,这个时候id 为16的第一次插入成功,第二次插入的时候失败,库中留存第一次的数据

    qqq
    无事务运行

    2、propagation_required,默认事务的传播行为required,在进行实验2的时候将表中id为16的数据先删除以免影响接下来的测试。@Transactional(propagation=Propagation.REQUIRED) ==  @Transactional这两个的作用是一样的没有事务创建一个事务执行,

    ![
    qq'q](http://college.creditease.cn/resources/upload/image/20200804/1596529605681003338.jpeg)
    事务的传播特行为为required
    结果是因为主键冲突将事务进行了回滚,所以两条数据都没有插入进去。

    3、propagation_supports,如果当前程序存在事务就加入该事务运行,如果不存在事务则在非事务中运行

    qqq

    事务的传播行为性为supports
    因为调用方未用事务那么就在非事务中运行,所以插入了first的第一条数据。

    4、propagation_mandatory,必须在一个事务中运行,否则就会抛出异常mandatory 这个单词有强制性的意思我们默认用required 而不用mandatory,是因为mandatory不能自动创建事务。

    qqq
    事务的传播行为为manatory
    因为调用的外层没有事务,所以两条数据没有插入。大家想想下面这种写法会发生什么现象
    qqq
    事务的传播行为mandatory

    5、propagation_ required _new,不管事务是不是存在,都会另起一个事务,如果事务存在则将当前事务挂起,重新执行新加的事务

    qqq
    事务的传播行为required_new
    结果和require一样,两条数据都没有入库,唯一健冲突导致第一条数据回滚,大家可以思考下我下面这两种情况。

    qqq

    情景1新起的事务抛出异常会不会让外围事务回滚?

    情景2外围事务失败会不会导致新起事务已提交的回滚?

    6、 propagation_ not _support,表示不在事务中运行,如果当前存在事务则将事务挂起

    ![qqq
    ](http://college.creditease.cn/resources/upload/image/20200804/1596529886418082560.jpeg)
    事务的传播行为not_suppoted
    这种情景下,如果你根据我的思路一步走的应该可以想到id 为17的入库,第二条主键冲突虽然然而notSupportSonTransationsl()这个方法没有事务所以不影响第一条入库情况,但是外围事务id为16的要进行回滚了,所以库中只有一条数据id=17的。

    7、 propagation_never,表示当前方法不能运行在事务当中,如果有事务则会抛出异常---->Existing transaction found for transaction marked with propagation 'never'


    事务的传播行为NEVER

    8、 propagation_nested,这种嵌套的事务,外围如果没有事务则自己另起一个事务,可独立与外围事务进行单独的提交或者回滚(这句话不要理解错了),下面这个案例同样的数据一条也没有落入库中,


    事务的传播行为nested

    事务的传播行为级别简单的演示完毕

    作者:宜信技术学院,王巧敏

    ]]>
    灵魂拷问,上 Kubernetes 有什么业务价值? Fri, 20 Jun 2025 02:20:33 +0800 本文整理自 2020 年 7 月 22 日《基于 Kubernetes 与 OAM 构建统一、标准化的应用管理平台》主题线上网络研讨会。文章共分为上下两篇,本文为上篇,主要和大家介绍上 Kubernetes 有什么业务价值,以及什么是“以应用为中心”的 Kubernetes。下篇将跟大家具体分享如何构建“以应用为中心”的 Kubernetes。

    视频回顾链接:https://www.bilibili.com/video/BV1Dv411v7P4/

    关注阿里巴巴云原生公众号,回复 “0722” 即可下载 PPT

    非常感谢大家来到 CNCF 的直播,我是张磊,阿里云的高级技术专家,Kubernetes 项目资深维护者。同时也是 CNCF 应用交付领域 co-chair。我今天给大家带来的分享主题是《基于 Kubernetes 与 OAM 构建统一、标准化的应用管理平台》。在封面上有个钉钉群组二维码。大家可以通过这个二维码进入线上交流群。

    1.png

    上 Kubernetes 有什么业务价值?

    今天要演讲的主题是跟应用管理或者说是云原生应用交付是相关的。首先我们想要先回答这么一个问题:为什么我们要基于 Kubernetes 去构建一个应用管理平台?

    3.png

    上图是一个本质的问题,我们在落地 K8s 经常遇到的一个问题。尤其是我们的业务方会问到这么一个问题,我们上 Kubernetes 有什么业务价值?这时候作为我们 K8s 工程师往往是很难回答的。原因在哪里呢?实际上这跟 K8s 的定位是相关的。K8s 这个项目呢,如果去做一个分析的话,我们会发现 K8s 不是一个 PaaS 或者应用管理的平台。实际上它是一个标准化的能力接入层。什么是能力接入层呢?大家可以看一下下图。

    4.png

    实际上通过 Kubernetes 对用户暴露出来的是一组声明式 API,这些声明式 API 无论是 Pod 还是 Service 都是对底层基础设施的一个抽象。比如 Pod 是对一组容器的抽象,而 Deployment 是对一组 pod 的抽象。而 Service 作为 Pod 的访问入口,实际上是对集群基础设施:网络、网关、iptables 的一个抽象。Node 是对宿主机的抽象。Kubernetes 还提供了我们叫做 CRD(也就是 Custom Resource)的自定义对象。让你自己能够自定义底层基础设施的一个抽象。

    而这些抽象本身或者是 API 本身,是通过另外一个模式叫做控制器(Controller)去实现的。通过控制器去驱动我们的底层基础设施向我的抽象逼近,或者是满足我抽象定义的一个终态。

    所以本质来讲,Kubernetes 他的专注点是“如何标准化的接入来自于底层,无论是容器、虚机、负载均衡各种各样的一个能力,然后通过声明式 API 的方式去暴露给用户”。这就意味着 Kubernetes 实际用户不是业务研发,也不是业务运维。那是谁呢?是我们的平台开发者。希望平台开发者能够基于 Kubernetes 再去做上层的框架或者是平台。那就导致了今天我们的业务研发和业务运维对 Kubernetes 直接暴露出来的这一层抽象,感觉并不是很友好。

    这里的关键点在于,Kubernetes 对这些基础设施的抽象,跟业务研发和业务运维看待系统的角度是完全不同的。这个抽象程度跟业务研发和业务运维希望的抽象程度也是不一样的。语义完全对不上,使用习惯也是有很大的鸿沟。所以说为了解决这样一个问题,都在思考一些解决方法。怎么能让我 Kubernetes 提供的基础设施的抽象能够满足我业务研发和业务运维的一个诉求呢?怎么能让 Kubernetes 能够成为业务研发和业务运维喜欢的一个平台呢?

    方法一:把所有人都变成 Kubernetes 专家

    5.png

    假如我们所有人都是 Kubernetes 专家,那当然会喜欢 Kubernetes 对我提供的服务,这里给他发个 Kubernetes 的 PhD 博士。这里我强烈推荐阿里云和 CNCF 主办的云原生技术公开课。大家试试学完这门课程后,能不能变成 Kubernetes 专家。

    这个方法门槛比较高,因为每个人对于这个系统本身感兴趣程度不太一样,学习能力也不太一样。

    方法二:构建一个面向用户的应用管理平台

    业界常见的方法,大家会基于 Kubernetes 构建一个面向用户的应用管理平台,或者说是一个 PaaS,有人直接做成一个 Serverless。

    6.png

    那这个具体是怎么做呢?还是在 Kubernetes 之上,会搭建一个东西叫做上层应用管理平台,这个上层应用平台对业务研发和业务运维暴露出来一个上层的 API。比如说业务研发这一侧,他不太会暴露 Pod,Deployment 这样的抽象。只会暴露出来 CI/CD 流水线。或者说一个应用,WordPress,一个外部网站,暴露出这样一个上层的概念,这是第一个部分。

    第二部分,它也会给业务运维暴露出一组运维的 API。比如说:水平扩容,发布策略,分批策略,访问控制,流量配置。这样的话有一个好处,业务研发和业务运维面对的 API 不是 Kubernetes 底层的 API,不是 Node,不是 Service,不是 Deployment,不是我们的 CRD。是这样一组经过抽象经过封装后的 API。这样的业务研发和业务运维用起来会跟他所期望的 Ops 流水线,它所熟悉的使用体检有个天然的结合点。

    所以说只有这么做了之后,我们才能够跟我们的业务老大说,Kubernetes 的业务价值来了。实际上业务价值不是在 Kubernetes 这一层,而是在 Kubernetes 往上的这一层--"你的解决方案"。所以说这样的一个系统构建出来之后呢,实际上是对 Kubernetes 又做了一层封装。变成了很多公司都有的,比如说 Kubernetes 应用平台。这是一个非常常见的做法。相比于我们让研发运维变成 Kubernetes 专家来说会更加实际一点。

    但是我们在阿里也好,在很多社区的实际场景也好,它往往会伴随着这么一个问题。这个问题是:今天 Kubernetes 的生态是非常非常繁荣的,下图是我在 CNCF 截的图,好几百个项目,几千个可以让我们 Kubernetes 即插即用的能力。比如 istio,KEDA,Promethues 等等都是 Kubernetes 的插件。正是基于这么一个扩展性非常高的声明式 API 体系才会有了这么繁荣的 Kubernetes 生态。所以可以认为 Kubernetes 能力是无限的,非常强大。

    7.png

    可是这么一个无限能力,如果对接到一个非常传统的,非常经典的一个应用管理平台。比如说我们的 PaaS 上,如 Cloud Foundry。立刻就会发现一个问题,PaaS 虽然对用户提供的是很友好的 API,但是这个 API 本身是有限的,是难以扩展的。比如说 Cloud Foundry 要给用户使用,就有 Buildpack 这么一个概念,而不是 Kubernetes 所有的能力都能给用户去使用。其实几乎所有的 PaaS 都会存在这么一个问题。它往上暴露的是一个用户的API,是不可扩展的,是个有限集。

    下面一个非常庞大繁荣的 Kubernetes 生态,没办法直接给用户暴露出去。可能每使用一个插件就要重新迭代开发你的 PaaS,重新交付你的 PaaS。这个是很难接受的。

    传统 PaaS 的“能力困境”

    这问题是一个普遍存在的问题,我们叫做传统 PaaS 的“能力困境”。

    8.png

    本质上来说这个困境是什么意思呢?K8s 生态繁荣多样的应用基础设施能力,与业务开发人员日益增长的应用管理诉求,中间存在一个传统的 PaaS,他就会变成一个瓶颈。K8s 无限的能力无法让你的研发与运维立刻用到。所以传统 PaaS 就会成为一个显而易见的瓶颈。

    这样给我带来一个思考:我们能不能抛弃传统 PaaS 的一个做法,基于 K8s 打造高可扩展的应用管理平台。我们想办法能把 K8s 能力无缝的透给用户,同时又能提供传统 PaaS 比较友好的面向研发运维的使用体验呢?

    其实可以从另外一个角度思考这个问题:如何基于 K8s 打造高可扩展的应用管理平台,实际上等同于 如何打造一个“以应用为中心的”的 Kubernetes。或者说能不能基于 Kubernetes 去封装下,让它能够像 PaaS 一样,去面向我的实际用户去使用呢?这个就是我们要聊的关键点。 

    什么是“以应用为中心”的 Kubernetes

    特征一:通过原生的声明式 API 和插件体系,暴露面向最终用户的上层语义和抽象

    9.png

    我们不是说要在 Kubernetes 上盖一个 PaaS,或者说是盖一个大帽子,不干这件事情。因为 K8s 本身可以扩展,可以写一组 CRD,把我们要的 API 给装上去。比如 CI/CD 流水线,就可以像 Tektong 系统直接使用 pipeline。应用也可以通过某些项目直接暴露出来。运维这一侧的发布扩容等,都可以通过安装一个 Operator 去解决问题。当然也需要一些技术将这些运维策略绑定到应用或者流水线中。

    这就是我们第一个点,以应用为中心的 K8s 首先是暴露给用户的语义和 API,而不是非常底层的,比如 Service、Node 或者是 Ingress。可能用户都不知道什么意思,也不知道怎么写的。

    特征二:上层语义和抽象可插拔,可扩展,没有抽象程度锁定和任何能力限制

    10.png

    第二个点很重要,上层语义和抽象必须是可插拔的,必须是可扩展的,是无缝兼容利用 K8s 的可扩展能力的。并且也不应该有对抽象程度的锁定。

    举个例子:比如一个应用本身既可以是 Deployment,这是一个比较低程度的抽象。也可以是 Knative Service,这是一个相对来说高程度的抽象,相对于 deployment 来说比较简单,只有一个 PodTemplate。甚至可以更简单,可以是一个 Service,或者是个 Function。这个时候抽象程度就很高。如果基于 K8s 做一个以应用为中心的框架的话,它应该是能够暴露工作负载的多种抽象程度的。而不是说单独去使用 Knative,只能暴露出 Knative Service。假如我想使用 Knative 部署一个 Statefulset,这当然是不可以的。抽象程度是完全不一致的。所以我希望这个以应用为中心的 K8s 是没有抽象程度的锁定的。

    同时也不应该有能力的限制,什么叫没有能力的限制呢?比如从运维侧举个例子,运维侧有很多很多扩容策略、发布策略等等。如果我想新加一个策略能力,它应该是非常简单的,就像在 K8s 安装一个 Operator 一样非常简单,能 helm insatll 就能搞定,答案是必须的。假如需要添加一个水平扩容,直接 helm install vpa 就能解决。通过这种方式才能做一个以应用为中心的 Kubernetes。

    可以看到它跟我们的传统 PaaS 还是有很大区别的,它的可扩展能力非常非常强。它本质上就是一个 K8s,但是它跟专有的 Service,Knative,OpenFaaS 也不一样。它不会把抽象程度锁定到某一种 Workload 上,你的 Workload 是可以随意去定义。运维侧的能力也可以随意可插拔的去定义。这才是我们叫做一个以应用为中心的 Kubernetes。那么这么一个 Kubernetes 怎么做呢?

    后续我们将会在下篇文章中详细为大家解读如何构建“以应用为中心”的 Kubernetes?以及构建这么一个以用户为中心的 Kubernetes,需要做几个层级的事情。

    《云原生实践公开课》

    去年,CNCF 与 阿里云联合发布了《云原生技术公开课》已经成为了 Kubernetes 开发者的一门“必修课”。今天,阿里云再次集结多位具有丰富云原生实践经验的技术专家,正式推出《云原生实践公开课》。课程内容由浅入深,专注讲解“ 落地实践”。还为学习者打造了真实、可操作的实验场景,方便验证学习成果,也为之后的实践应用打下坚实基础。课程已经正式上线,欢迎大家观看。

    点击链接即可免费观看课程:https://developer.aliyun.com/learning/roadmap/cloudnative2020

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    阿里云开发者 DevUP 沙龙 -青岛站 -阿里巴巴微服务技术的应用与实践邀你参加啦 Fri, 20 Jun 2025 02:20:33 +0800 微服务开发中,SpringCloud作为Spring生态中的针对微服务的技术框架,越来越受到各个企业技术人员的追捧。但是,SpringCloud中一些组件,在实践使用中,存在一定的局限。SpringCloudAlibaba,横空出世,替换了SpringCloud中的一些组件,使微服务在实践中,能够更便捷的、更优雅的实现落地。

    活动亮点:

    1、面基!在青岛举办的线下技术沙龙。
    2、阵容!各类技术专家在线实践教学。
    3、干货!快速了解微服务相关知识。

    时间:8月22日(周四)13:30-16:10

    地点:山东省青岛市崂山区海尔路170号鑫裕和大厦8楼

    报名地址:https://survey.aliyun.com/apps/zhiliao/7irPO_3gv

    默认文件1597047215922.png

    ]]>
    物模型接入价值与实践 Fri, 20 Jun 2025 02:20:33 +0800 物模型价值

    物联网元年

    关键词:探索、快速

    2016年阿里云物联网平台(前称:物联网套件)上线,为客户设备上云提供了通道能力,包括MQTT连接、消息流转等核心功能。
    第一批客户大多基于该模式使用物联网平台能力,当时整个行业处于物联网云平台起步期,包括AWS,Azure起步阶段同样只是提供通道能力。
    基于通道能力,客户使用物联网平台接入方式详见文档 https://developer.aliyun.com/article/746536
    这个阶段的客户大多是硬件厂商,软硬一体开发,尝试物联网转型提升设备价值,对物联网平台的诉求比较简单,希望自己更多参与,对新模式有更多把控力,所以都会采用自定义协议上云。

    test

    物联网繁荣

    关键词:生态、扩展、数字化

    近两年物联网设备、解决方案如雨后春笋般涌出,不少用户希望赶上物联网这波浪潮。这个阶段的客户不仅仅关注设备连云,也开始关注围绕设备产生的解决方案。因此客户角色从硬件厂商,快速扩展到集成商、软件提供商等。由于大量角色的进入,对软硬开发解耦、易扩展的能力提出了诉求。同时我们也发现第一批使用通道能力的平台客户随着自己业务发展、设备扩展,原来的架构已无法支撑,对物联网平台也提出了新的要求。

    举两个典型场景:

    • 老客户升级:某个共享设备提供商,原来仅提供大学校园共享洗衣机服务,利用物联网平台通道能力上云,随着公司业务发展,从共享洗衣机业务扩展到校园淋浴、饮水机、充电桩等多类设备,原来自定义协议和API无法支撑多品类设备,难扩展。需要有一套接入标准和规范,方便快速扩展设备类型。
    • 新生态客户:某个充电桩平台客户,提供充电桩管理平台,作为甲方要求大量桩企(乙方)按照平台规范接入,典型的软硬件分离场景。需要有一套接入标准和规范,方便快速扩展桩企规模。

    这一阶段平台在通道能力之上,提供了物模型能力,物模型可以屏蔽底层设备差异,让软件开发者基于平台提供的标准API开发;硬件开发者基于平台提供的标准协议开发;从而达到软硬开发解耦的目的。

    test

    物联网赋能

    关键词:场景化、智能

    物联网终极目标一定是基于设备采集数据赋能业务,实现数字业务化。例如金融、物流、家居、餐饮、商场、医疗、交通等不同领域通过物联网数字化后,结合数据分析智能化决策、互联互通、场景规则、数字孪生等能力实现纵深领域场景化、智能化。
    这一阶段平台在通道能力、物模型能力之上,还进一步提供设备智能运维、数据分析、可视化、数字孪生等高价值服务,帮助客户数字化后产生真正的业务价值。

    test

    基于以上分析,物联网已经过了最初的“元年”阶段,也迈入了“繁荣”阶段,正逐步朝“问物联网要赋能”的阶段演进。物模型是物联网生态化、高扩展、数字化、智能化非常重要的基础,强烈建议客户使用。

    物模型接入实践

    自定义接入模式

    以一个老客户为例,原来仅使用物联网平台通道能力,下图中1~8流程都需要自定义开发,当客户设备类型足够简单时,该模式复杂度通常不会成为客户痛点。

    test

    面临的挑战

    随着客户接入设备种类越来越多,面临的扩展性问题也越来越严峻。

    test

    使用物模型后的模式

    物模型模式下,设备与云交互协议、云平台设备API都基于物模型标准化了,即使设备不断扩展,客户业务服务器和设备端逻辑都不需要进行调整,保证了扩展性。

    test

    物模型接入流程详细介绍

    流程图

    以下是客户详细接入流程,主要分为:云端配置、设备开发、服务端开发、设备运行时管理四大部分。平台会提供一些工具,使各部分流程更高效。接下来进行详细介绍。
    image.png
    本文试图手把手介绍从0到1接入物模型,还会配套介绍一些接入过程中有帮助的平台能力,所以文章篇幅比较长,事实上客户接入流程还是非常简单的,真正开发只需要涉及到图中红色三个模块。

    如果您希望快速接入,可以直接关注P0部分,其它部分都可以跳过。

    1 云端配置

    1.1 创建产品(P0)

    1.登录物联网平台
    2.创建产品。
    image.png
    说明
    • 所属品类:标准品类库提供了一些供参考的模板,选择后可以修改,建议使用。
    • 节点类型:根据实际选择即可。
    • 数据格式:“ICA标准数据格式(Alink JSON)”表示设备使用标准Alink JSON格式上报数据;“透传/自定义”表示设备可以使用自定义格式,通过Alink特定Topic上报物联网平台,该模式客户需要写脚本进行转换,透传模式在此不做展开,后面单独起文章介绍。

    1.2 物模型建模(P0)

    1.模型查看。
    已有的模型是继承自创建产品时选择的“充电桩”品类模板。
    image.png

    2.编辑模型。
    通过“编辑草稿”,进行修改和添加,最后需要对物模型“发布上线”。
    image.png
    说明
    • 定义物模型非常重要,物模型通过属性、事件、服务三要素描述了设备所有能力,设备和云交互、客户服务器访问设备通过物模型都可以实现协议标准化。如果客户定义的物模型如果足够通用和专业,阿里可以帮助作为ICA行业标准进行推广。
    • 服务的调用方式有:同步调用、异步调用两种模式。客户云端开发调用下行控制API,同步调用和异步调用获取返回结果方式不一样,在后文“3.3”章节详细介绍。

    物模型概念介绍
    物模型介绍文档请参见这里
    了解物模型概念,能够帮助您更好对设备建模。

    1.3 物模型配置

    当前默认是物模型强校验模式,即设备上报数据在IoT平台会进行物模型数据规范强校验,如果不符合规范会报错。
    另外物模型弱校验、免校验、去重等规则也会在近期陆续开放,后期进行文档补充。
    配置之后,会在设备运行时生效。

    关联阅读:4.2 物模型扩展规则校验。

    1.4 注册三元组(P0)

    1.注册设备。
    image.png
    说明
    • 添加设备:测试阶段使用较多,单个添加。
    • 批量添加:量产阶段使用,有两种模式,“自动生成”表示设备标识符(deviceName)由平台按照一定的规则随机颁发;“批量上传”支持客户自定义设备标识符(deviceName)。
    2.查看设备列表。
    可以通过“设备列表”、“批次管理”两种方式查看创建的设备列表。
    image.png
    通过“批次管理”查看这一批次设备详情,并且支持下载三元组列表。
    image.png

    注意:此处设备标识符(deviceName)非常重要,与productKey, deviceSecret一起称为设备的“三元组”,作为设备的唯一身份,大部分情况需要烧录到设备上。

    2 设备开发

    2.1 使用设备SDK开发(P0)

    设备接入SDK文档请参见这里
    image.png

    根据需要选择合适的语言版本。C SDK 建议使用“4.x”版本。

    本文选择 Java SDK进行演示。
    环境准备:https://help.aliyun.com/document_detail/97331.html
    物模型开发:https://help.aliyun.com/document_detail/97333.html

    1.开发之前需要先准备如下好两份数据:

    • 设备证书信息(productKey、deviceName、deviceSecret)
      image.png
    • 设备物模型
      image.png

    为了方便查看物模型详细数据规范,通过导出“物模型TSL”查看详细物模型定义,其中包括物模型属性、事件、服务标识符、参数、数据规范。抽取部分内容,针对以下属性、事件、服务在DEMO中进行开发演示。

        "schema":"https://iotx-tsl.oss-ap-southeast-1.aliyuncs.com/schema.json",
        "profile":{
            "productKey":"a1nhbEV****"
        },
        "properties":[
            {
                "identifier":"acOutMeterIty",
                "name":"交流输出电表底值监测属性",
                "accessMode":"rw",
                "required":false,
                "dataType":{
                    "type":"int",
                    "specs":{
                        "min":"0",
                        "max":"200",
                        "step":"1"
                    }
                }
            }
        ],
        "events":[
            {
                "identifier":"post",
                "name":"post",
                "type":"info",
                "required":true,
                "desc":"属性上报",
                "method":"thing.event.property.post",
                "outputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ]
            },
            {
                "identifier":"startChaResEvt",
                "name":"启动充电结果事件",
                "type":"info",
                "required":false,
                "method":"thing.event.startChaResEvt.post",
                "outputData":[
                    {
                        "identifier":"gunNum",
                        "name":"充电枪编号",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ]
            }
        ],
        "services":[
            {
                "identifier":"set",
                "name":"set",
                "required":true,
                "callType":"async",
                "desc":"属性设置",
                "method":"thing.service.property.set",
                "inputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ],
                "outputData":[
    
                ]
            },
            {
                "identifier":"get",
                "name":"get",
                "required":true,
                "callType":"async",
                "desc":"属性获取",
                "method":"thing.service.property.get",
                "inputData":[
                    "acOutMeterIty"
                ],
                "outputData":[
                    {
                        "identifier":"acOutMeterIty",
                        "name":"交流输出电表底值监测属性",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"200",
                                "step":"1"
                            }
                        }
                    }
                ]
            },
            {
                "identifier":"startChaResService",
                "name":"开启充电",
                "required":false,
                "callType":"async",
                "method":"thing.service.startChaResService",
                "inputData":[
                    {
                        "identifier":"charm",
                        "name":"电量",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"1",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ],
                "outputData":[
                    {
                        "identifier":"realcharm",
                        "name":"realcharm",
                        "dataType":{
                            "type":"int",
                            "specs":{
                                "min":"0",
                                "max":"100",
                                "step":"2"
                            }
                        }
                    }
                ]
            }
        ]
    }

    2.开发代码。
    如下示例中只需要将三元组,和属性、事件、服务参数替换成您的设备信息。其它代码可以直接运行。

    关于免订阅能力介绍:

    有些设备最资源比较敏感,为了避免初始化订阅大量Alink协议中系统Topic带来的性能开销,平台提供了免订阅能力,即平台帮设备进行Topic订阅。
    SDK只有3.1.0及以后版本支持免订阅能力,并且默认打开该能力。
    如果3.1.0及以后版本SDK您希望取消免订阅,依旧按需订阅Topic,可以设置SDK配置项关闭该能力,在make.settings中设置“FEATURE_MQTT_AUTO_SUBSCRIBE=n”。

    public class Demo {
    
        public static void main(String[] args) throws Exception {
    
            String pk = "a1nhbEVCP**";
            String dn = "7mBP6Dd6IT27Rt***";
            String ds = "*****";
    
            /**
             * 连接 & 认证
             */
            LinkKitInitParams params = new LinkKitInitParams();
    
            // 设置 Mqtt 初始化参数
            IoTMqttClientConfig config = new IoTMqttClientConfig();
            config.productKey = pk;
            config.deviceName = dn;
            config.deviceSecret = ds;
            config.receiveOfflineMsg = false;
            params.mqttClientConfig = config;
    
            // 设置初始化三元组信息,用户传入
            DeviceInfo deviceInfo = new DeviceInfo();
            deviceInfo.productKey = pk;
            deviceInfo.deviceName = dn;
            deviceInfo.deviceSecret = ds;
    
            params.deviceInfo = deviceInfo;
    
            LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
                public void onError(AError aError) {
                    System.out.println("===============FAILURE===============");
                    ALog.e(TAG, "Init Error error=" + aError);
                    System.out.println("===============FAILURE===============");
                }
    
                public void onInitDone(InitResult initResult) {
                    System.out.println("===============SUCCESS===============");
                    ALog.i(TAG, "onInitDone result=" + initResult);
                    System.out.println("===============SUCCESS===============");
                }
    
            });
    
            //此处sleep 5S,由于上面init是异步流程
            Thread.sleep(5000);
    
            /**
             * 物模型开发
             */
    
            /**
             * 上报属性
             */
            Map<String, ValueWrapper> properties = new HashMap<>();
    
            // key为物模型中属性标识符"acOutMeterIty",value需要遵循属性值规范:int类型,取值范围在0~200之间;
            properties.put("acOutMeterIty", new ValueWrapper(10));
    
            LinkKit.getInstance().getDeviceThing().thingPropertyPost(properties, new IPublishResourceListener() {
    
                @Override
                public void onSuccess(String s, Object o) {
                    System.out.println("=====thingPropertyPost success=======");
                    System.out.println(s);
                    System.out.println(JSON.toJSONString(o));
                }
    
                @Override
                public void onError(String s, AError aError) {
                    System.out.println("=====thingPropertyPost failure=======");
                }
            });
    
            // 上报属性之后,云端会返回响应结果,此处是监听云端返回的属性reply
            LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
    
                @Override
                public void onNotify(String s, String s1, AMessage aMessage) {
                    System.out.println("===PROPERTY REPLY===");
                    System.out.println("TOPIC:" + s1);
                    System.out.println("Payload:" + JSON.toJSONString(aMessage));
                }
    
                @Override
                public boolean shouldHandle(String s, String s1) {
                    return false;
                }
    
                @Override
                public void onConnectStateChange(String s, ConnectState connectState) {
                }
            });
    
            /**
             * 上报事件
             */
            HashMap<String, ValueWrapper> eventMap = new HashMap<>();
    
            // key为物模型中事件参数的标识符"gunNum", value为事件参数值需要遵循数值规范:int类型,取值范围0~100之间;
            eventMap.put("gunNum", new ValueWrapper.IntValueWrapper(50));
    
            OutputParams eventOutput = new OutputParams(eventMap);
    
            // 参数identity为"startChaResEvt"属于物模型事件标识符。
            LinkKit.getInstance().getDeviceThing().thingEventPost("startChaResEvt", eventOutput, new IPublishResourceListener() {
                public void onSuccess(String resId, Object o) {
                    System.out.println("=====thingEventPost success=======");
                    System.out.println(resId);
                    System.out.println(JSON.toJSONString(o));
                }
    
                public void onError(String resId, AError aError) {
                    System.out.println("=====thingEventPost failure=======");
                }
            });
    
            /**
             * 监听并执行下行服务
             */
            // 获取设备支持的所有服务
            LinkKit.getInstance().getDeviceThing().getServices();
    
            // 用户可以根据实际情况注册自己需要的服务的监听器
            List<Service> srviceList = LinkKit.getInstance().getDeviceThing().getServices();
    
            for (int i = 0; srviceList != null && i < srviceList.size(); i++) {
                Service service = srviceList.get(i);
    
                LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), new ITResRequestHandler() {
    
                    public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
    
                        System.out.println("onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
                        System.out.println("收到云端异步服务调用 " + identify);
                        try {
                            /**
                             * 设置属性(property)的模式
                             */
                            // "set"为设置属性默认的标识符
                            if ("set".equals(identify)) {
                                // TODO 用户需要设置真实设备的的属性
                                /**
                                 * 向云端同步设置好的属性值
                                 */
                                Map<String, ValueWrapper> desiredProperty = (Map<String, ValueWrapper>) ((InputParams) result).getData();
    
                                LinkKit.getInstance().getDeviceThing().thingPropertyPost(desiredProperty, new IPublishResourceListener() {
    
                                    @Override
                                    public void onSuccess(String s, Object o) {
                                        if (result instanceof InputParams) {
                                            Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                            //                        data.get()
                                            ALog.d(TAG, "收到异步下行数据 " + data);
                                            // 响应云端 接收数据成功
                                            itResResponseCallback.onComplete(identify, null, null);
                                        } else {
                                            itResResponseCallback.onComplete(identify, null, null);
                                        }
                                    }
    
                                    @Override
                                    public void onError(String s, AError aError) {
                                        AError error = new AError();
                                        error.setCode(100);
                                        error.setMsg("setPropertyFailed.");
                                        itResResponseCallback.onComplete(identify, new ErrorInfo(error), null);
                                    }
                                });
    
                                /**
                                 * 服务(service)的模式
                                 */
                                // "startChaResService"为服务的标识符
                            } else if ("startChaResService".equals(identify)) {
    
                                Map<String, ValueWrapper> inputParams = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                // TODO 根据服务入参inputParams执行设备逻辑,比如启动充电
                                // 充电完成后,向云端返回输出参数
                                OutputParams outputParams = new OutputParams();
                                // key为"charm"属于物模型中"startChaResService"服务出参标识符,value为出参值遵循数据规范:int类型,数据范围1~100之间;
                                outputParams.put("charm", new ValueWrapper.IntValueWrapper(20));
    
                                itResResponseCallback.onComplete(identify, null, outputParams);
    
                            } else {
                                // 根据不同的服务做不同的处理,跟具体的服务有关系
                                OutputParams outputParams = new OutputParams();
                                // 根据特定服务,按照服务规范返回服务的出参。
                                itResResponseCallback.onComplete(identify, null, outputParams);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            ALog.d(TAG, "云端返回数据格式异常");
                        }
                    }
                    public void onSuccess(Object o, OutputParams outputParams) {
                        ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
                        ALog.d(TAG, "注册服务成功");
                    }
                    public void onFail(Object o, ErrorInfo errorInfo) {
                        ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
                        ALog.d(TAG, "注册服务失败");
                    }
                });
            }
        }
    }

    说明
    • 上报属性成功,云端会返回REPLY,有以下日志说明设备到云,云到设备的链路全部走通。
    image.png
    • 设备收到属性设置指令,在完成物理设备属性修改后,建议将最新属性同步上报云端。

    2.2 不使用SDK开发

    1.协议准备。
    “2.1 使用设备SDK开发”介绍了使用阿里云提供的SDK进行设备开发,当然您也可以选择不使用SDK,完全基于Alink协议(设备和云交互协议)开发。
    Alink协议文档:https://help.aliyun.com/document_detail/90459.html
    重点关注物模型协议部分:https://help.aliyun.com/document_detail/89301.html 。里面包含了物模型相关所有Topic介绍(物模型Topic列表在控制台也可以查看,如下图)。

    test

    文档详细介绍了设备端如何向云端上报“属性”、“事件”,如何订阅云端向下发送的“服务”指令。
    Topic和Payload都基于客户定义的物模型进行标准化和规范化,从而使得客户设备与云交互方式不会随着设备类型变化而改变,满足扩展性要求。
    image.png

    2.环境准备。
    根据自己选型选择合适的MQTT客户端,本文选择eclipse paho。

    <dependency>
        <groupId>org.eclipse.paho</groupId>
        <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
        <version>1.1.1</version>//可以选择您需要的版本
    </dependency>

    3.开发。
    物模型复用“2.1 使用设备SDK开发”中“开发前准备”给出的。

    关于免订阅能力介绍:

    有些设备最资源比较敏感,为了避免初始化订阅大量Alink协议中系统Topic带来的性能开销,平台提供了免订阅能力,即平台帮设备进行Topic订阅。
    SDK只有3.1.0及以后版本支持免订阅能力,并且默认打开该能力。
    如果不使用SDK开发,可以通过设备端在MQTT的连接报文中的clientId部分, 新增_ss=1表示开启自动订阅, 建连成功后服务端会自动订阅上以下表格中的topic, 若传递 _ss=0 或者不传递该字段, 则不会发生服务端自动订阅动作。

    4.上报属性。

    String productKey = "a1nhbEV****";
    String deviceName = "7mBP6Dd6IT2*****";
    String deviceSecret = "****";
    
    // MQTT连接
    MqttTestClient client;
    client = new MqttTestClient(productKey, deviceName, deviceSecret);
    
    client.connect();
    
    String setTopic = "/thing/event/property/post";
    String setTopicReply = "/thing/event/property/post_reply";
    
    // 上报属性,云端会返回REPLY,进行订阅。(为了节省端侧订阅开销,可以开通免订阅)
    // 此处client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client subscribe
    client.sysTopic(setTopicReply).subscribe();
    
    // 封装Alink协议系统参数
    Map<String, Object> payload = new HashMap<String, Object>();
    Map<String, Object> params = new HashMap<String, Object>();
    payload.put("id", 11);//id需要保证设备端一段时间内唯一
    payload.put("params", params);
    payload.put("method", "thing.event.property.post");
    
    // 组装属性payload
    String propKey = "acOutMeterIty";
    int statusValue = 30;
    Map<String, Object> proValue = new HashMap<>();
    proValue.put("value", statusValue);
    proValue.put("time", System.currentTimeMillis());
    params.put(propKey, proValue);
    
    // 上报(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client publish消息)
    client.sysTopic(setTopic).publish(JSON.toJSONString(payload));
    
    // 打印云端返回的Reply(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client监听订阅消息)
    client.sysTopic(setTopicReply).readTopic(10000);
    
    client.disconnect();

    日志打印的设备请求和响应。
    image.png

    5.上报事件。

    
    String productKey = "a1nhbEV****";
    String deviceName = "7mBP6Dd6IT27*****";
    String deviceSecret = "***";
    
    // MQTT连接
    MqttTestClient client;
    client = new MqttTestClient(productKey, deviceName, deviceSecret);
    
    client.connect();
    
    // topic中为"startChaResEvt"属于物模型事件标识符。
    String setTopic = "/thing/event/startChaResEvt/post";
    String setTopicReply = "/thing/event/startChaResEvt/post_reply";
    
    // 报事件,云端会返回REPLY,进行订阅。(为了节省端侧订阅开销,可以开通免订阅)
    client.sysTopic(setTopicReply).subscribe();
    
    // 封装Alink协议系统参数
    Map<String, Object> payload = new HashMap<String, Object>();
    Map<String, Object> params = new HashMap<String, Object>();
    payload.put("id", 11);//id需要保证设备端一段时间内唯一
    payload.put("params", params);
    payload.put("method", "thing.event.startChaResEvt.post");
    
    // 组装属性payload
    Map<String, Object> dataValue = new HashMap<>();
    // key为物模型中事件参数的标识符"gunNum", value为事件参数值需要遵循数值规范:int类型,取值范围0~100之间;
    dataValue.put("gunNum", 59);
    
    params.put("value", dataValue);
    params.put("time", System.currentTimeMillis());
    
    // 上报(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client publish消息)
    client.sysTopic(setTopic).publish(JSON.toJSONString(payload));
    
    // 打印云端返回的Reply(client进行了封装,您根据自己的业务进行封装即可,也可以直接使用MQTT Client监听订阅消息)
    client.sysTopic(setTopicReply).readTopic(10000);
    
    client.disconnect();

    6.服务调用。
    此处为一段伪代码。可以在MQTT建连的时候通过callback监听云端下发的控制指令或消息。
    前提:已经对下行的TOPIC进行订阅过,免订阅能力参考上面介绍。

    mqttClient = new MqttClient(url, clientId, persistence);
    final MqttConnectOptions connOpts = new MqttConnectOptions();
    connOpts.setMqttVersion(4);
    connOpts.setAutomaticReconnect(true);
    connOpts.setCleanSession(false);
    connOpts.setUserName(mqttUsername);
    connOpts.setPassword(mqttPassword.toCharArray());
    connOpts.setKeepAliveInterval(65);
    LogUtil.log(clientId + "进行连接, 目的地: " + url);
    
    // 此处订阅云端下发的消息
    mqttClient.setCallback(new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
            LogUtil.log("connection lost, cause:" + cause);
            cause.printStackTrace();
        }
    
        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            TopicChannel topicChannel = getTopic(topic);
            LogUtil.log("receive message, channel:" + topicChannel
                        + ",topic:" + topic
                        + ", payload:" + new String(message.getPayload(), "UTF-8") + "");
            topicChannel.put(message);
        }
    
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
            //如果是qos 0消息 token.resp是没有回复的
            LogUtil.log("sent, " + ((token == null || token.getResponse() == null) ? "null"
                                    : token.getResponse().getKey()));
        }
    });
    
    mqttClient.connect(connOpts);

    重点说明
    • 所有被订阅的下行Topic都会被监听到。物模型相关的主要包括:属性上报Reply、属性下行设置、服务下行控制。
    • 设置设备属性(https://help.aliyun.com/document_detail/89301.html#title-wmh-y2e-18r),默认异步方式返回结果。
    • 订阅的Topic为Alink协议标准Topic:“/sys/{productKey}/{deviceName}/thing/service/property/set”
    • 服务控制(https://help.aliyun.com/document_detail/89301.html#title-3pt-nfy-jys),同异步方式取决于物模型中service配置的调用模式。
    • 服务异步方式订阅的Topic为Alink协议标准Topic:“/sys/{productKey}/{deviceName}/thing/service/{tsl.service.identifier}”
    • 服务同步方式订阅的Topic需要遵循RRPC Topic模式:详见文档https://help.aliyun.com/document_detail/90568.html

    注意:仅设备侧需要感知RRPC特殊TOPIC,设备上云后,数据流转、开放API面向的还是Alink协议编程。

    2.3 在线调试

    设备开发后之后,如何快速模拟业务服务器给设备下发指令,调试设备能力?IoT平台提供了“在线调试”的功能,可以模拟设备或模拟应用端到端调试。

    test

    此处使用“在线调试”里面“调试真实设备”能力。通过控制台下发设备控制指令,分两类:1)属性设置;2)服务调用。

    1.服务调用调试。

    image.png

    image.png
    云端下发后,可以到设备端查看控制Log是否打印,以判断指令达到端侧。
    从图中可见设备收到startChaResService服务,同时向云端返回了输出参数。

    2.属性设置调试。

    image.png
    说明
    • “获取”:暂不支持到设备,只能从云端获取设备最新属性。
    • “设置”:指令直接到设备端,设备修改本地属性之后,上报云端最新属性;到设备上的设置指令为"set"。
    • “设置期望值”:如果设备在线,会直接下发设备,如果设备离线,指令在云端进行缓存,待上线后下发设备端,下发之后,设备修改本地属性之后,同样上报云端最新属性;到设备上的设置指令同样为"set"。如果您希望使用物模型期望值能力,可点击查看最佳实践。
    image.png
    云端下发后,可以到设备端查看控制Log是否打印,以判断指令达到端侧。
    从图中可见设备收到set指令,返回了服务响应,同时向云端上报了最新属性。

    说明:服务结果还可以通过“2.4 查看物模型数据”章节中获取。

    2.4 查看物模型数据

    DEMO运行之后,可以看到设备已经“在线”状态。
    “运行状态”展示设备上报的属性值;
    “事件管理”展示设备上报的事件;
    “服务调用”展示云端下发设备的控制服务;
    image.png

    上报属性结构化展示。
    image.png

    上报事件,包括事件参数展示。
    image.png

    属性设置、服务调用两类服务的云端下发入参、设备响应出参都有展示,如上证明设备收到云端指令,并且正常返回响应。

    2.5 查看日志服务

    设备在运行过程,可能会出现一些异常,比如连接失败、认证失败、数据异常等等,为了便于排查,可以查看日志服务。举例设备上报数据可能会不符合物模型规范,比如事件参数"gunNum"对应值的数据范围为0~100之间,而真实上报了50000。日志服务会展示设备错误详情。
    image.png

    image.png
    可以看到日志内容为“{"Reason":"tsl parse: int value is bigger than max 100 -> gunNum"}”,说明gunNum对应值超过物模型规范最大值100的限制。物模型规范详情到“物模型TSL”查看。

    image.png
    同时可以通过“日志转储”中“日志报表”进一步查看设备大盘,包括设备上下线次数、设备上线IP区域分布、设备消息量、设备消息量Top列表、物模型错误分布、云端API错误分布等多维度指标。
    日志服务介绍文档请参见这里

    3 服务端开发

    设备连接到阿里云IoT平台,设备数据会保存在IoT平台时序数据库。同时IoT平台提供两种方式供客户获取设备数据:方式1)通过服务端订阅或者规则引擎实时流转到客户服务器;2)通过开放API供客户调用获取。

    3.1 服务端调用API开发(P0)

    1.环境准备。
    SDK下载文档:https://help.aliyun.com/document_detail/30581.html
    API接口列表:https://help.aliyun.com/document_detail/69579.html
    重点关注物模型使用相关API

    test

    2.以下示例为设置设备属性API,设备异步返回结果,客户需要通过“数据流转”方式获取。

    String accessKey = "***";
    String accessSecret = "***";
    try {
        DefaultProfile.addEndpoint("cn-shanghai", "cn-shanghai", "Iot", "iot.cn-shanghai.aliyuncs.com");
    } catch (Exception e) {
        System.out.println("DefaultProfile exception");
    }
    
    IClientProfile profile = DefaultProfile.getProfile("cn-shanghai", accessKey, accessSecret);
    DefaultAcsClient defaultAcsClient = new DefaultAcsClient(profile);
    
    SetDevicePropertyRequest setDevicePropertyRequest = new SetDevicePropertyRequest();
    // 如果使用实例,此处传入真实实例id;如果公共实例,不需要设置。
    //createProductRequest.setIotInstanceId("iothub-test-xxx");
    setDevicePropertyRequest.setProductKey(pk);
    setDevicePropertyRequest.setDeviceName(dn);
    
    Map<String, Integer> properties = new HashMap<>();
    // key为物模型中属性标识符"acOutMeterIty",value需要遵循属性值规范:int类型,取值范围在0~200之间;
    properties.put("acOutMeterIty", 98);
    setDevicePropertyRequest.setItems(JSON.toJSONString(properties));
    
    SetDevicePropertyResponse response = null;
    try {
        response = defaultAcsClient.getAcsResponse(setDevicePropertyRequest);
    } catch (Exception e) {
        Log.error("执行失败:e:" + e.getMessage());
    }
    
    System.out.println("===============");
    System.out.println("setDeviceProperty request : " + JSON.toJSONString(setDevicePropertyRequest));
    System.out.println("setDeviceProperty response : " + JSON.toJSONString(response.getData()));
    System.out.println("setDeviceProperty requestId : " + response.getRequestId());
    System.out.println("===============");

    重点说明
    image.png
    下行控制如果为异步服务,需要通过订阅数据流转获取设备返回结果,订阅方式和数据结构详见“3.2 数据流转”章节介绍。

    关联介绍:“3.2.1 服务端订阅”中“重点说明”。

    3.2 数据流转

    平台提供两种数据流转方式:方式1)服务端订阅;方式2)规则引擎;

    3.2.1服务端订阅(P0)

    服务端订阅配置
    image.png
    “推送消息类型”选择“设备上报消息”,包括物模型属性上报、事件上报、设备下行指令结果(包括属性设置响应、服务控制响应)等消息。
    消息格式详见文档:https://help.aliyun.com/document_detail/73736.html

    test

    服务端订阅DEMO
    接入说明:https://help.aliyun.com/document_detail/143601.html

    /**
     * AMQP服务端订阅
    */
    //参数说明,请参见AMQP客户端接入说明文档。
    String accessKey = "***";
    String accessSecret = "***";
    String consumerGroupId = "***";
    //iotInstanceId:购买的实例请填写实例ID,公共实例请填空字符串""。
    String iotInstanceId = "";
    long timeStamp = System.currentTimeMillis();
    //签名方法:支持hmacmd5、hmacsha1和hmacsha256。
    String signMethod = "hmacsha1";
    //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
    //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
    String clientId = "TESTClientID";
    
    //userName组装方法,请参见AMQP客户端接入说明文档。
    String userName = clientId + "|authMode=aksign"
        + ",signMethod=" + signMethod
                + ",timestamp=" + timeStamp
                + ",authId=" + accessKey
                + ",iotInstanceId=" + iotInstanceId
                + ",consumerGroupId=" + consumerGroupId
                + "|";
    //计算签名,password组装方法,请参见AMQP客户端接入说明文档。
    String signContent = "authId=" + accessKey + "&timestamp=" + timeStamp;
    String password = doSign(signContent,accessSecret, signMethod);
    //接入域名,请参见AMQP客户端接入说明文档。
    String connectionUrl = "amqps://${uid}.iot-amqp.${regionId}.aliyuncs.com:5671?amqp.idleTimeout=80000";
    
    Hashtable<String, String> hashtable = new Hashtable<>();
    hashtable.put("connectionfactory.SBCF",connectionUrl);
    hashtable.put("queue.QUEUE", "default");
    hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
    Context context = new InitialContext(hashtable);
    ConnectionFactory cf = (ConnectionFactory)context.lookup("SBCF");
    Destination queue = (Destination)context.lookup("QUEUE");
    // Create Connection
    Connection connection = cf.createConnection(userName, password);
    ((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);
    // Create Session
    // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
    // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    connection.start();
    // Create Receiver Link
    MessageConsumer consumer = session.createConsumer(queue);
    consumer.setMessageListener(messageListener);
    }
    
    private static MessageListener messageListener = new MessageListener() {
        @Override
        public void onMessage(Message message) {
            try {
                //1.收到消息之后一定要ACK。
                // 推荐做法:创建Session选择Session.AUTO_ACKNOWLEDGE,这里会自动ACK。
                // 其他做法:创建Session选择Session.CLIENT_ACKNOWLEDGE,这里一定要调message.acknowledge()来ACK。
                // message.acknowledge();
                //2.建议异步处理收到的消息,确保onMessage函数里没有耗时逻辑。
                // 如果业务处理耗时过程过长阻塞住线程,可能会影响SDK收到消息后的正常回调。
                executorService.submit(() -> processMessage(message));
            } catch (Exception e) {
                logger.error("submit task occurs exception ", e);
            }
        }
    };
    
    /**
     * 在这里处理您收到消息后的具体业务逻辑。
    */
    private static void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String content = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            System.out.println("AMQP receive message"
                               + ", topic = " + topic
                               + ", messageId = " + messageId
                               + ", content = " + content);
        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }
    
    private static JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            logger.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }
    
        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            logger.error("onConnectionFailure, {}", error.getMessage());
        }
    
        /**
          * 连接中断。
          */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            logger.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }
    
        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            logger.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }
    
        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {}
    
        @Override
        public void onSessionClosed(Session session, Throwable cause) {}
    
        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {}
    
        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {}
    };
    
    /**
     * 计算签名,password组装方法,请参见AMQP客户端接入说明文档。
     */
    private static String doSign(String toSignString, String secret, String signMethod) throws Exception {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), signMethod);
        Mac mac = Mac.getInstance(signMethod);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(toSignString.getBytes());
        return Base64.encodeBase64String(rawHmac);
    }

    日志打印出订阅到的流转消息如下,符合预期。
    image.png

    重点说明
    下行控制如果为异步服务,需要通过订阅数据流转获取设备返回结果。订阅Topic为"/sys/{productKey}/{deviceName}/thing/downlink/reply/message",需要根据"requestId"关联请求和响应。

    关联介绍:“3.1 服务端调用API开发”中“重点说明”。

    image.png

    3.2.2 规则引擎数据订阅。

    配置SQL
    SQL介绍文档这里
    image.png

    调试SQL
    Payload数据格式文档这里
    image.png

    可以查看“调试结果”。

    test

    符合配置的SQL结果。

    转发数据
    可以转发到客户以下多种云产品中,本文选择AMQP作为示例验证。
    image.png

    image.png
    创建完成后,需要到规则列表页“启动”改规则。

    订阅数据
    服务端订阅代码可以复用上面“3.1”服务端订阅代码。差别就是服务端订阅,订阅的是Topic对应的完整Payload;而规则引擎流转AMQP,在消息流转过程可以对Payload做一些规则过滤或简单计算。
    以下日志精简报文是通过规则引擎过滤后获取的数据。
    image.png
    说明:同一组数据不要同时开通规则引擎和服务端订阅两种订阅模式,避免消息干扰。

    4 设备运行时

    设备量产之后,到达消费者手上,会开始激活上线进入到设备运行时。由于不属于开发态流程,本章节仅做简单介绍,目的是能让开发者知道开发态的配置在运行态如何产生作用,对设备接上阿里云IoT平台后的流程有个简单的认识。

    image.png

    本文通过物模型接入流程,介绍了平台设备连接、物模型规范校验、物模型数据、规则引擎、服务端订阅、开放API六大基础能力。
    设备全生命周期过程中,还有不少设备管理能力供客户选择,其中包括设备标签、设备分组、设备检索、OTA、设备运维、设备分发、文件上传、远程配置等,欢迎使用。

    4.1 连接

    设备连接过程,云端会对设备进行身份认证。

    4.2 物模型规范校验

    由于目前物模型配置仅提供强校验模式,物模型规范校验主要对设备上报的报文进行Alink协议解析、物模型数据规范校验。平台后续会陆续开放弱校验、免校验、数据去重能力。

    关联阅读:1.3 物模型配置

    4.3 设备管理能力

    4.3.1 设备标签
    介绍文档:https://help.aliyun.com/document_detail/73733.html
    4.3.2 设备分组
    介绍文档:https://help.aliyun.com/document_detail/90386.html
    4.3.3 OTA
    介绍文档:https://help.aliyun.com/document_detail/85700.html
    4.3.4 设备分发
    介绍文档:https://help.aliyun.com/document_detail/143450.html

    ]]>
    语雀的技术架构演进之路 Fri, 20 Jun 2025 02:20:33 +0800

    作者 | 不四

    每个技术人心中或多或少都有一个「产品梦」,好的技术需要搭配好的产品,才能让用户爱不释手,尤其是做一款知识服务型产品。

    作者何翊宇(花名:不四,微信:dead_horse)是蚂蚁金服体验技术部高级前端技术专家,语雀产品技术负责人。本文从技术架构的视角,回顾了语雀的原型、内部服务和对外商业化的全过程,并对函数计算在语雀架构演进过程中所扮演的角色做了详细的介绍。

    语雀是一个专业的云端知识库,用于团队的文档协作。现在已是阿里员工进行文档编写和知识沉淀的标配,并于 2018 年开始对外提供服务。

    原型阶段

    回到故事的开始。

    2016 年,语雀孵化自蚂蚁科技,当时,蚂蚁金融云需要一个工具来承载它的文档,负责的技术同学利用业余时间,搭建了这个文档工具。项目的初期,没有任何人员和资源支持,同时也是为了快速验证原型,技术选型上选择了最低成本的方案。

    底层服务完全基于体验技术部内部提供的 BaaS 服务和容器托管平台:

    • Object 服务:一个类 MongoDB 的数据存储服务;
    • File 服务:阿里云 OSS 的基础上封装的一个文件存储服务;
    • DockerLab:一个容器托管平台;

    image.png
    这些服务和平台都是基于 Node.js 实现的,专门给内部创新型应用使用,也正是由于有这些降低创新成本的内部服务,才给工程师们提供了更好的创新环境。

    语雀的应用层服务端,自然而然的选用了蚂蚁体验技术部开源的 Node.js Web 框架 Egg(蚂蚁内部的封装 Chair),通过一个单体 Web 应用实现服务端。应用层客户端也选用了 React 技术栈,结合内部的 antd,并采用 CodeMirror 实现了一个功能强大、体验优雅的 markdown 在线编辑器。

    当时仅仅是一个工程师的业余项目,采用内部专为创新应用提供的 BaaS 服务和一系列的开源技术,验证了在线文档工具这个产品原型。

    内部服务阶段

    2017年,随着语雀得到团队内部的认可,他的目标已经不仅仅是金融云的文档工具,而是成为阿里所有员工的知识管理平台。不仅面向技术人员 Markdown 编辑器,还向非技术知识创作者,提供了富文本编辑器,并选择了更“Web”的路线,在富文本编辑器中加入了公式、文本绘图、思维导图等特色功能。而随着语雀在知识管理领域的不断探索,知识管理的三层结构(团队、知识库、文档)开始成型。

    在此之上的协作、分享、搜索与消息动态等功能越来越复杂单纯的依靠 BaaS 服务已经无法满足语雀的业务需求了。

    为了应对业务发展带来的挑战,我们主要从下面几个点进行改造:

    • BaaS 服务虽然使用简单成本低,但是它们提供的功能不足以满足语雀业务的发展,同时稳定性上也有不足。所以我们将底层服务由 BaaS 替换成了阿里云的 IaaS 服务(MySQL、OSS、缓存、搜索等服务)。
    • Web 层仍然采用了 Node.js 与 Egg 框架,但是业务层借鉴 rails 社区的实践开始变成了一个大型单体应用,通过引入 ORM 构建数据模型层,让代码的分层更清晰。
    • 前端编辑器从 codeMirror 迁移到 Slate。为了更好的实现语雀编辑器的功能,我们内部 fork 了 Slate 进行深入开发,同时也自定义了一个独立的内容存储格式,以提供更高效的数据处理和更好的兼容性。

    image.png
    在内部服务阶段,语雀已经成为了一个正式的产品,通过在阿里内部的磨炼,语雀的产品形态基本定型。

    对外商业化阶段

    随着语雀在内部的影响力越来越大,一些离职出去创业的阿里校友们开始找到玉伯(蚂蚁体验技术部研究员):“语雀挺好用的,有没有考虑商业化之后让外面的公司也能够用起来?”

    经过小半年的酝酿和重构,2018 年初,语雀开始正式对外提供服务,进行商业化。

    当一个应用走出公司内到商业化环境中,面临的技术挑战一下子就变大了。最核心的知识创作管理部分的功能越来越复杂,表格、思维导图等新格式的加入,多人实时协同的需求对编辑器技术提出了更高的挑战。而为了更好的服务企业用户与个人用户,语雀在企业服务、会员服务等方面也投入了很大精力。在业务快速发展的同时,服务商业化对质量、安全和稳定性也提出了更高的要求。

    为了应对业务发展,语雀的架构也随之发生了演进:

    我们将底层的依赖完全上云,全部迁移到了阿里云上,阿里云不仅仅提供了基础的存储、计算能力,同时也提供了更丰富的高级服务,同时在稳定性上也有保障。

    • 丰富的云计算基础服务,保障语雀的服务端可以选用最适合语雀业务的的存储、队列、搜索引擎等基础服务;
    • 更多人工智能服务给语雀的产品带来了更多的可能性,包括 OCR 识图、智能翻译等服务,最终都直接转化成为了语雀的特色服务;

    而在应用层,语雀的服务端依然还是以一个基于 Egg 框架的大型的 Node.js Web 应用为主。但是随着功能越来越多,也开始将一些相对比较独立的服务从主服务中拆出去,可以把这些服务分成几类:

    • 微服务类:例如多人实时协同服务,由于它相对独立,且长连接服务不适合频繁发布,所以我们将其拆成了一个独立的微服务,保持其稳定性。
    • 任务服务类:像语雀提供的大量本地文件预览服务,会产生一些任务比较消耗资源、依赖复杂。我们将其从主服务中剥离,可以避免不可控的依赖和资源消耗对主服务造成影响。
    • 函数计算类:类似 Plantuml 预览、Mermaid 预览等任务,对响应时间的敏感度不高,且依赖可以打包到阿里云函数计算中,我们会将其放到函数计算中运行,既省钱又安全。

    随着编辑器越来越复杂,在 slate 的基础上进行开发遇到的问题越来越多。最终语雀还是走上了自研编辑器的道路,基于浏览器的 Contenteditable 实现了富文本编辑器,通过 Canvas 实现了表格编辑器,通过 SVG 实现了思维导图编辑器。
    image.png
    语雀的这个阶段(也是现在所处的阶段)是商业化阶段,但是我们仍然保持了一个很小的团队,通过 JavaScript 全栈进行研发。底层的服务全面上云,借力云服务打造语雀的特色功能。同时为企业级用户和个人知识工作者者提供知识创作和管理工具。

    和函数计算的不解之缘

    语雀是一个复杂的 Web 应用,也是一个典型的数据密集型应用(Data-Intensive Application),背后依赖了大量的数据库等云服务。语雀服务端是 Node.js 技术栈。当提到 node 的时候,可能立刻就会有几个词浮现在我们脑海之中:单线程(single-threaded)、非阻塞(non-blocking)、异步(asynchronously programming),这些特性一方面非常的适合于构建可扩展的网络应用,用来实现 Web 服务这类 I/O 密集型的应用,另一方面它也是大家一直对 node 诟病的地方,对 CPU 密集型的场景不够友好,一旦有任何阻塞进程的方法被执行,整个进程就被阻塞。

    像语雀这样用 node 实现整个服务端逻辑的应用,很难保证不会出现一些场景可能会消耗大量 CPU 甚至是死循环阻塞进程的,例如以 markdown 转换举例,由于用户的输入无法穷举,总有各种可能让转换代码进入到一个低效甚至是死循环的场景之中。在 node 刚出世的年代,很难给这些问题找到完美的解决办法,而即便是 Java 等基于线程并发模型的语言,在遇到这样的场景也很头痛,毕竟 CPU 对于 web 应用来说都是非常重要的资源。而随着基础设置越来越完善,当函数计算出现时,node 最大的短板看起来有了一个比较完美的解决方案。

    阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传,只需要为代码实际运行所消耗的资源付费,代码未运行则不产生费用。

    把函数计算引入之后,我们可以将那些 CPU 密集型、存在不稳定因素的操作统统放到函数计算服务中去执行,而我们的主服务再次回归到了 I/O 密集型应用模型,又可以愉快的享受 node 给我们带来的高效研发福利了!

    以语雀中遇到的一个实际场景来举例,用户传入了一些 HTML 或者 Markdown 格式的文档内容,我们需要将其转换成为语雀自己的文档格式。在绝大部分情况下,解析用户输入的内容都很快,然而依然存在某些无法预料到的场景会触发解析器的 bug 而导致死循环的出现,甚至我们不太敢升级 Markdown 解析库和相关插件以免引入更多的问题。但是随着函数计算的引入,我们将这个消耗 CPU 的转换逻辑放到函数计算上,语雀的主服务稳定性不会再被影响。
    image.png

    除了帮助 Web 系统分担一些 CPU 密集型操作以外,函数计算还能做什么呢?

    在语雀上我们支持各种代码形式来绘图,包括 Plantuml、公式、Mermaid,还有一些将文档导出成 PDF、图片等功能。这些场景有两个特点:

    • 他们依赖于一些复杂的应用软件,例如 Puppeteer、Graphviz 等;
    • 可能需要执行用户输入的内容;

    支持这类场景看似简单,通过 process.exec 子进程调用一下就搞定了。但是当我们想把它做成一个稳定的对外服务时,问题就出现了。这些复杂的应用软件可能从设计上并没有考虑要长期运行,长期运行时的内存占用、稳定性可能会有一些问题,同时在被大并发调用时,对 CPU 的压力非常大。再加上有些场景需要运行用户输入的代码,攻击者通过构建恶意输入,可以在服务器上运行攻击代码,非常危险。

    在没有引入函数计算之前,语雀为了支持这些功能,尽管单独分配了一个任务集群,在上面运行这些三方服务,接受主服务的请求来避免影响主服务的稳定性。但是为了解决上面提到的一系列问题还需要付出很大的成本:

    • 需要维持一个不小的任务集群,尽管可能大部分时间都用不上那么多资源。
    • 需要定时对三方应用软件进行重启,避免长时间运行带来的内存泄露,即便如此有些特殊请求也会造成第三方软件的不稳定。
    • 对用户的输入进行检测和过滤,防止黑客恶意攻击,而黑客的攻击代码很难完全防住,安全风险依旧很大。

    image.png
    最后语雀将所有的第三方服务都分别打包在函数中,将这个任务集群上的功能都拆分成了一系列的函数放到了函数计算上。通过函数计算的特点一下解决了上面的所有问题:

    • 函数计算的计费模式是按照代码实际运行的 CPU 时间计费,不需要长期维护一个任务集群了。
    • 函数计算上的函数运行时尽管会有一些常驻函数的优化,但是基本不用考虑长期运行带来的一系列问题,且每次调用之间都相互独立,不会互相影响。
    • 用户的输入代码是运行在一个沙箱容器中,即便不对用户输入做任何过滤,恶意攻击者也拿不到任何敏感信息,同时也无法进入内部网络执行代码,更加安全。

    image.png

    除了上面提到的这些功能之外,语雀最近还使用 OSS + 函数计算替换了之前使用的阿里云视频点播服务来进行视频和音频的转码。

    由于浏览器可以直接支持播放的音视频格式并不多,大量用户上传的视频想要能够直接在语雀上进行播放需要对它们进行转码,业界一般都是通过 FFmpeg 来对音视频进行转码的。转码服务也是一个典型的 CPU 密集型场景,如果要自己搭建视频转码集群会面临大量的资源浪费,而使用阿里云视频点播服务,成本也比较高,而且能够控制的东西也不够多。函数计算直接集成了 FFmpeg 提供音视频处理能力,并集成到应用中心,配合 SLS 完善了监控和数据分析。语雀将音视频处理从视频点播服务迁移到函数计算之后,通过优化压缩率、减少不必要的转码等优化,将费用降低至之前的 1/5。
    image.png
    从语雀的实践来看,语雀并没有像 SFF 一样将 Web 服务迁移到函数计算之上(SFF 模式并不是现在的函数计算架构所擅长的),但是函数计算在语雀整体的架构中对稳定性、安全性和成本控制起到了非常重要的作用。总结下来函数计算非常适合下面几种场景:

    • 对于时效性要求不算非常高的 CPU 密集型操作,分担主服务 CPU 压力。
    • 当做沙箱环境执行用户提交的代码。
    • 运行不稳定的三方应用软件服务。
    • 需要很强动态伸缩能力的服务。

    在引入函数计算之后,语雀现阶段的架构变成了以一个 Monolith Application 为核心,并将一些独立的功能模块根据使用场景和对能力的要求分别拆分成了 Microservices 和 Serverless 架构。应用架构与团队成员组成、业务形态息息相关,但是随着各种云服务与基础设施的完善,我们可以更自如的选择更合适的架构。
    image.png
    为什么要特别把 Serverless 单独拿出来说呢?还记得之前说 Node.js 是单线程,不适合 CPU 密集型任务么?

    由于 Serverless 的出现,我们可以将这些存在安全风险的,消耗大量 CPU 计算的任务都迁移到函数计算上。它运行在沙箱环境中,不用担心用户的恶意代码造成安全风险,同时将这些 CPU 密集型的任务从主服务中剥离,避免出现并发时阻塞主服务。按需付费的方式也可以大大节约成本,不需要为低频功能场景部署一个常驻服务。所以我们会尽量的把这类服务都迁移到 Serverless 上(如阿里云函数计算)。

    结语 | 语雀的技术栈选择

    语雀这几年一步步发展过来,背后的技术一直在演进,但是始终遵循了几条原则:

    • 技术栈选型要匹配产品发展阶段。产品在不同的阶段对技术提出的要求是不一样的,越前期,对迭代效率的要求越高,商业化规模化之后,对稳定性、性能的要求就会变高。不需要一上来就用最先进的技术方案,而是需要和产品阶段一起考虑和权衡。
    • 技术栈选型要结合团队成员的技术背景。语雀选择 JavaScript 全栈的原因是孵化语雀的团队,大部分都是 JavaScript 背景的程序员,同时 Node.js 在蚂蚁也算是一等公民,配套的设施相对完善。
    • 最重要的一点是,不论选择什么技术栈,安全、稳定、可维护(扩展)都是要考虑清楚的。用什么语言、用什么服务会变化,但是这些基础的安全意识、稳定性意识,如何编写可维护的代码,都是决定项目能否长期发展下去的重要因素。

    image.png
    关注「Alibaba F2E」
    把握阿里巴巴前端新动向

    ]]>
    掌门教育微服务体系Solar第3弹:Nacos企业级落地下篇 Fri, 20 Jun 2025 02:20:33 +0800 1.png

    联席作者:谢璐 谢庆芳 伊安娜 任浩军
    郑重鸣谢:Nacos - 彦林,Spring Cloud Alibaba - 小马哥、洛夜,Nacos 社区 - 张龙(pader)、春少(chuntaojun)

    相关文章推荐:

    前言

    在高速发展的时候,公司规模越来越大,老师人数越来越多,这时候公司不能铺太多人去做运营与服务,必须提高每个人效,这就需要技术驱动。因此掌门教育转变成一家技术驱动型的公司,如果被迫成为一家靠资金驱动的公司就活不下去了。

    -- 张翼(掌门教育创始人兼CEO)

    掌门教育自2014年正式转型在线教育以来,秉承“让教育共享智能,让学习高效快乐”的宗旨和愿景,经历云计算、大数据、人工智能、 AR / VR / MR 以及现今最火的 5G ,一直坚持用科技赋能教育。掌门教育的业务近几年得到了快速发展,特别是今年的疫情,使在线教育成为了新的风口,也给掌门教育新的机遇。

    随着业务规模进一步扩大,流量进一步暴增,微服务数目进一步增长,使老的微服务体系所采用的注册中心 Eureka 不堪重负,同时 Spring Cloud 体系已经演进到第二代,第一代的 Eureka 注册中心已经不大适合现在的业务逻辑和规模,同时它目前被 Spring Cloud 官方置于维护模式,将不再向前发展。如何选择一个更为优秀和适用的注册中心,这个课题就摆在了掌门人的面前。经过对 Alibaba NacosHashiCorp Consul等开源注册中心做了深入的调研和比较,最终选定 Alibaba Nacos 做微服务体系 Solar 中的新注册中心。

    背景故事

    基础架构部选择新的注册中心,测试组需要配合对业界成熟的注册中心产品做分析和比较。由于掌门教育采用的是比较纯净的 Spring Cloud 技术栈,所以我们需要围绕它的注册中心,从测试角度,进行功能和性能上研究。

    Spring Cloud 技术栈官方支持 Netflix EurekaHashiCorp ConsulZookeeper 三个注册中心,它们可以相互间实现无缝迁移,Alibaba Nacos 是新加盟 Spring Cloud 技术栈的新成员。测试组的同学们对上述四个注册中心做了一一研究和分析,鉴于时间紧迫,除了 EurekaNacos 之外,其它两个中间件未做深入的功能测试和性能测试。下面提供来自阿里巴巴 Nacos 官方某次业界宣讲的资料截图以供大家参考:

    • Eureka 介绍

    2.png

    • Zookeeper 介绍

    3.png

    • Consul 介绍

    4.png

    • 上述三个注册中心比较

    5.png

    本文将围绕 Alibaba Nacos 着重针对其功能测试和性能测试两方面进行剖析和介绍。

    Nacos 测试篇

    Nacos 性能测试

    ① Nacos Server 性能测试

    开发部署了 UATNacos ,测试亲自压测。

    • 核心脚本
    def registry(ip):
        fo = open("service_name.txt", "r")
        str = fo.read()
        service_name_list = str.split(";")
        service_name = service_name_list[random.randint(0,len(service_name_list) - 1)]
        fo.close()
        client = nacos.NacosClient(nacos_host, namespace='')
        print(client.add_naming_instance(service_name,ip,333,"default",1.0,{'preserved.ip.delete.timeout':86400000},True,True))
        while True:
          print(client.send_heartbeat(service_name,ip,333,"default",1.0,"{}"))
          time.sleep(5)
    • 压测数据

    表格 1.jpg

    • 压测结果图

    6.png

    7.png

    Nacos Server 是3台 1C4G 集群,同时承受1499个服务和12715个实例注册,而且 CPU 和内存长期保持在一个合适的范围内,果真 Nacos 性能是相当 OK 的。

    Nacos 功能测试

    ① Nacos Server 接口测试

    表格2.jpg

    更多更详 API 请参见 Nacos 官方文档: Open API 指南

    https://nacos.io/zh-cn/docs/open-api.html

    ② Nacos Eureka Sync 测试

    • 交叉注册

    网关,服务 A ,服务 B 各10台实例,网关注册 EurekaA 注册 NacosB 注册 Eureka ,同步正常,可调用。

    • 压力测试

    请求大于100万次,查看 Sync Server 会不会受到影响,结果 ErrorRequest = 0,同步服务数和实例数没有变化。

    8.png

    • 有无损调用

    网关 Sync Server 挂掉,网关服务 Eureka 同步 Nacos 失败,不影响网关 -> A -> B 调用。

    • 自动创建同步

    发布系统第一次发布应用到 Eureka / Nacos ,会自动创建 Eureka -> Nacos 的同步任务或 Nacos -> Eureka 的同步任务

    9.png

    • 减少 Sync Server

    Sync Server 4C8G ,停止机器,逐台递减,结论:平均1台 4C8G 机器最大可同步100个服务。

    • 增加 Sync Server

    2台 Etcd 节点,停机一台,Etcd 读取超时,结论:600个服务至少2台 Etcd 节点,这里重点强调,新增服务时, Hash 算法虚拟节点数,务必和原有的保持一致,不然会出现同步失败,影响跨注册中心调用。

    10.png

    • 重启 Sync Server

    增加 Sync Server 个数,重启 Sync Server ,各节点同步数重新计算且均衡。

    ③ Nacos Client 功能测试

    Nacos Client 界面重点测试集群管理,服务列表和权限控制。

    • Nacos Server 重启后,集群管理界面正常展示3台集群节点 IP
    • 服务注册 Nacos Server 后,服务列表新增注册上去的服务名和实例个数,而且可查看详情。

    11.png

    • 服务上下线操作,健康状态和元数据等展示正常。
    • 编辑,删除等操作只有具备 Admin 权限的人员才可操作。

    ④ Nacos Client 自动化测试

    • 自动化测试链路

    全链路测试路径

    API网关 -> 服务A(两个实例) -> 服务B(两个实例)

    12.png

    全链路服务部署

    表格3.jpg

    • 自动化测试入口

    结合 Spring Boot JunitTestApplication.class 为测试框架内置应用启动程序, MyTestConfiguration 用于初始化所有测试用例类。在测试方法上面加入 JUnit@Test注解

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = { TestApplication.class, MyTestConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class MyTest {
        @Autowired
        private MyTestCases myTestCases;
    
        private static long startTime;
    
        @BeforeClass
        public static void beforeTest() {
            startTime = System.currentTimeMillis();
        }
    
        @AfterClass
        public static void afterTest() {
            LOG.info("* Finished automation test in {} seconds", (System.currentTimeMillis() - startTime) / 1000);
        }
    
        @Test
        public void testNoGray() throws Exception {
            myTestCases.testNoGray(gatewayTestUrl);
            myTestCases.testNoGray(zuulTestUrl);
        }
    
        @Test
        public void testVersionStrategyGray() throws Exception {
            myTestCases.testVersionStrategyGray1(gatewayGroup, gatewayServiceId, gatewayTestUrl);
            myTestCases.testVersionStrategyGray1(zuulGroup, zuulServiceId, zuulTestUrl);
        }
    }
    @Configuration
    public class MyTestConfiguration {
        @Bean
        public MyTestCases myTestCases() {
            return new MyTestCases();
        }
    }
    • 基于 Nacos Client 的普通调用自动化测试

    在测试方法上面增加注解 @DTest ,通过断言 Assert 来判断测试结果。注解 @DTest 内容如下:

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface DTest {
    
    }

    代码如下:

    public class MyTestCases {
        @Autowired
        private TestRestTemplate testRestTemplate;
    
        @DTest
        public void testNoGray(String testUrl) {
            int noRepeatCount = 0;
            List<String> resultList = new ArrayList<String>();
            for (int i = 0; i < 4; i++) {
                String result = testRestTemplate.getForEntity(testUrl, String.class).getBody();
    
                LOG.info("Result{} : {}", i + 1, result);
    
                if (!resultList.contains(result)) {
                    noRepeatCount++;
                }
                resultList.add(result);
            }
    
            Assert.assertEquals(noRepeatCount, 4);
        }
    }
    • 基于 Nacos Client 的灰度蓝绿调用自动化测试

    在测试方法上面增加注解 @DTestConfig ,通过断言 Assert 来判断测试结果。注解 DTestConfig 注解内容如下:

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface DTestConfig {
        // 组名
        String group();
    
        // 服务名
        String serviceId();
    
        // 组名-服务名组合键值的前缀
        String prefix() default StringUtils.EMPTY;
    
        // 组名-服务名组合键值的后缀
        String suffix() default StringUtils.EMPTY;
    
        // 执行配置的文件路径。测试用例运行前,会把该文件里的内容推送到远程配置中心或者服务
        String executePath();
    
        // 重置配置的文件路径。测试用例运行后,会把该文件里的内容推送到远程配置中心或者服务。该文件内容是最初的默认配置
        // 如果该注解属性为空,则直接删除从配置中心删除组名-服务名组合键值
        String resetPath() default StringUtils.EMPTY;
    }

    代码如下:

    public class MyTestCases {
        @Autowired
        private TestRestTemplate testRestTemplate;
    
        @DTestConfig(group = "#group", serviceId = "#serviceId", executePath = "gray-strategy-version.xml", resetPath = "gray-default.xml")
        public void testVersionStrategyGray(String group, String serviceId, String testUrl) {
            for (int i = 0; i < 4; i++) {
                String result = testRestTemplate.getForEntity(testUrl, String.class).getBody();
    
                LOG.info("Result{} : {}", i + 1, result);
    
                int index = result.indexOf("[V=1.0]");
                int lastIndex = result.lastIndexOf("[V=1.0]");
    
                Assert.assertNotEquals(index, -1);
                Assert.assertNotEquals(lastIndex, -1);
                Assert.assertNotEquals(index, lastIndex);
            }
        }
    }

    初始默认无灰度蓝绿的配置文件 gray-default.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <rule>
    
    </rule>

    灰度蓝绿生效的配置文件 gray-strategy-version.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <rule>
        <strategy>
            <version>1.0</version>
        </strategy>
    </rule>
    • 基于 Nacos Client 的自动化测试报告样例
    ---------- Run automation testcase :: testStrategyCustomizationGray() ----------
    Header : [a:"1", b:"2"]
    Result1 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    Result2 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    Result3 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    Result4 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    * Passed
    ---------- Run automation testcase :: testVersionRuleGray() ----------
    Result1 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    Result2 : zuul -> solar-service-a[192.168.0.107:3001][V=1.0][R=dev][G=solar-group] -> solar-service-b[192.168.0.107:4001][V=1.0][R=qa][G=solar-group]
    Result3 : zuul -> solar-service-a[192.168.0.107:3002][V=1.1][R=qa][G=solar-group] -> solar-service-b[192.168.0.107:4002][V=1.1][R=dev][G=solar-group]
    Result4 : zuul -> solar-service-a[192.168.0.107:3001][V=1.0][R=dev][G=solar-group] -> solar-service-b[192.168.0.107:4001][V=1.0][R=qa][G=solar-group]
    * Passed

    Nacos 测试总结

    Nacos 不仅性能好,而且界面简洁,这样的注册中心你值得拥有。

    作者介绍

    • 吴毅挺,掌门技术副总裁,负责技术中台和少儿技术团队。曾就职于百度、eBay 、携程,曾任携程高级研发总监,负责从零打造携程私有云、容器云、桌面云和 PaaS 平台。
    • 任浩军,掌门基础架构部负责人。曾就职于平安银行、万达、惠普,曾负责平安银行平台架构部 PaaS 平台 Halo 基础服务框架研发。10 多年开源经历,Github ID:@HaojunRen,Nepxion 开源社区创始人,Nacos Group Member,Spring Cloud Alibaba & Nacos & Sentinel & OpenTracing Committer。

    参与 Nacos 落地的基础架构部成员,包括:

    • 童子龙,张彬彬,廖梦鸽,张金星,胡振建,谢璐,谢庆芳,伊安娜

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    一图看懂阿里云 @KubeCon 2020(含 PPT 下载) Fri, 20 Jun 2025 02:20:33 +0800 头图.png

    近日,首届 CNCF 与全球开源志愿者发起的 KubeCon 2020 线上峰会已圆满落幕,吸引了来自 30+ 个国家 5800+ 参会者观看。作为云原生领域最受开发者欢迎的平台,阿里云以 27 场演讲位列话题丰富度第一位,并于会上详解了阿里云 ACK Pro、ASM、ACR EE、ACK@Edge 等四款企业级容器新品,分享了对云原生创新基础设施和云原生操作系统进化的思考。

    <关注公众号,回复 “KubeCon” 即可下载 PPT>

    活动谢幕,你是否学有所获呢?

    不要担心,下文将带你回顾 KubeCon 2020 峰会上阿里云的全部干货亮点!

    【首日亮点全解读】

    1.jpeg

    【次日亮点全解读】

    2.jpeg

    【一图看懂阿里云要点精彩】

    3.png

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    查看Socket断开原因及加入心跳机制防止自动断开连接 Fri, 20 Jun 2025 02:20:33 +0800 【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107804286

    一般情况下,前端页面连接WebSocket服务的时候都是通过Nginx等负载均衡,然后由Nginx去代理连接后端的socket服务。Nginx的配置类似如下:

    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    location / {
        proxy_pass https://socket;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    如果建立连接之后不做一些措施,那么可能会有各种各样的原因会导致socket断开,最好在socket断开时将错误打印出来。

    ws.onclose = function (ev) {
      console.log('socket 断开: ' + ev.code + ' ' + ev.reason + ' ' + ev.wasClean)
    }

    socket断开时,会触发CloseEvent, CloseEvent会在连接关闭时发送给使用 WebSocket 的客户端,它在 WebSocket 对象的 onclose 事件监听器中使用。 CloseEvent有三个字段需要注意, 通过分析这三个字段,一般就可以找到断开原因:

    • CloseEvent.code: code是错误码,是整数类型
    • CloseEvent.reason: reason是断开原因,是字符串
    • CloseEvent.wasClean: wasClean表示是否正常断开,是布尔值。一般异常断开时,该值为false
    状态码 名称 描述
    0–999 保留段, 未使用.
    1000 CLOSE_NORMAL 正常关闭; 无论为何目的而创建, 该链接都已成功完成任务.
    1001 CLOSE_GOING_AWAY 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
    1002 CLOSE_PROTOCOL_ERROR 由于协议错误而中断连接.
    1003 CLOSE_UNSUPPORTED 由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据).
    1004 保留. 其意义可能会在未来定义.
    1005 CLOSE_NO_STATUS 保留. 表示没有收到预期的状态码.
    1006 CLOSE_ABNORMAL 保留. 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧).
    1007 Unsupported Data 由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据).
    1008 Policy Violation 由于收到不符合约定的数据而断开连接. 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景.
    1009 CLOSE_TOO_LARGE 由于收到过大的数据帧而断开连接.
    1010 Missing Extension 客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接.
    1011 Internal Error 客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接.
    1012 Service Restart 服务器由于重启而断开连接.
    1013 Try Again Later 服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接.
    1014 由 WebSocket标准保留以便未来使用.
    1015 TLS Handshake 保留. 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书).
    1016–1999 由 WebSocket标准保留以便未来使用.
    2000–2999 由 WebSocket拓展保留使用.
    3000–3999 可以由库或框架使用.? 不应由应用使用. 可以在 IANA 注册, 先到先得.
    4000–4999 可以由应用使用.

    为了保证socket稳定,不断开,最好也是最简单的办法是添加一些逻辑,一直保持socket处在连接的状态。常见的做法就是间隔发ping消息给服务端,服务端接收到这个消息之后返回pong消息,以此来保持心跳,以防sock断开。我们常见的ping消息和pong消息实际上是发送了一个文本消息,这个消息的内容是ping或者pong,甚至是heatbeat等等,但是从socket协议来说是有设计ping消息和pong消息的。在socket的数据帧中,有一个opcode,它表明了socket的数据帧是什么类型的:

    • %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
    • %x1:表示这是一个文本帧(frame)
    • %x2:表示这是一个二进制帧(frame)
    • %x3-7:保留的操作代码,用于后续定义的非控制帧。
    • %x8:表示连接断开。
    • %x9:表示这是一个ping操作。
    • %xA:表示这是一个pong操作。
    • %xB-F:保留的操作代码,用于后续定义的控制帧。

    规范的心跳应该是在opcode里定义type:ping(9)才对,消息的内容是null,什么都没有,这才是最轻量级最规范的websocket心跳机制。一般情况下,使用发文本消息的方式也是没啥问题的,无非就是多消耗了一点流量和带宽,调试起来也容易一些,有可能心跳消息本身就会带一些业务数据。
    js代码如下:

    var lockReconnect = false;  
    var ws = null;          
    var wsUrl = 'wss://127.0.0.1/socket'
    createWebSocket(wsUrl);  
    
    function createWebSocket(url) {
        try{
            if('WebSocket' in window){
                ws = new WebSocket(url);
            }
            initEventHandle();
        }catch(e){
            reconnect(url);
            console.log(e);
        }     
    }
    
    function initEventHandle() {
        ws.onclose = function (ev) {
            reconnect(wsUrl);
             console.log('socket 断开: ' + ev.code + ' ' + ev.reason + ' ' + ev.wasClean)
        };
        ws.onerror = function (ev) {
            reconnect(wsUrl);
            console.log("llws连接错误!");
        };
        ws.onopen = function () {
            heartCheck.reset().start();      
            console.log("llws连接成功!"+new Date().toLocaleString());
        };
        ws.onmessage = function (message) {    
            heartCheck.reset().start();      //拿到任何消息都说明当前连接是正常的
            console.log("llws收到消息啦:" +message.data);
            if(message.data!='pong'){
                var msg = JSON.parse(message.data);
            }
        };
    }
    
    // 当窗口关闭时,主动去关闭websocket连接
    window.onbeforeunload = function() {
        ws.close();
    }  
    
    function reconnect(url) {
        if(lockReconnect) return;
        lockReconnect = true;
        setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
            createWebSocket(url);
            lockReconnect = false;
        }, 2000);
    }
     
    var heartCheck = {
        timeout: 3000,       
        timeoutObj: null,
        serverTimeoutObj: null,
        reset: function(){
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function(){
            var self = this;
            this.timeoutObj = setTimeout(function(){ 
                ws.send("ping");
                console.log("ping!")
                self.serverTimeoutObj = setTimeout(function(){
                  //如果超过一定时间还没重置,说明后端主动断开了
                    ws.close();      
                }, self.timeout)
            }, this.timeout)
        }
    }

    服务端Java代码:

    @OnMessage  
    public void onMessage(String message, Session session) {  
            if(message.equals("ping")){
    
            }else{
                   
            }
    }

    欢迎关注 “后端老鸟” 公众号,接下来会发一系列的专题文章,包括Java、Python、Linux、SpringBoot、SpringCloud、Dubbo、算法、技术团队的管理等,还有各种脑图和学习资料,NFC技术、搜索技术、爬虫技术、推荐技术、音视频互动直播等,只要有时间我就会整理分享,敬请期待,现成的笔记、脑图和学习资料如果大家有需求也可以公众号留言提前获取。由于本人在所有团队中基本都处于攻坚和探路的角色,搞过的东西多,遇到的坑多,解决的问题也很多,欢迎大家加公众号进群一起交流学习。

    【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107804286

    image

    ]]>
    SpringCloud--微服务架构 Fri, 20 Jun 2025 02:20:33 +0800 @[toc]
    在这里插入图片描述

    微服务架构

    • 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调。互相配合,每个服务运行在其独立的进程中,服务与服务之间采用轻量级通信机制互相协作(通常基于HTTP协议的RESTful API)。每个服务都围绕这具体业务进行构建,并且能够被独立的部署到生产环境
      通俗的讲 就是 一个个springboot 开发出来的模块就是落地功能


    • 而我们 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

    在这里插入图片描述

    • SpringCloud = 分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的几何体,俗称微服务全家桶

    SpringCloud的基础功能:
    : 服务治理: Spring Cloud Eureka
    客户端负载均衡: Spring Cloud Ribbon
    服务容错保护: Spring Cloud Hystrix
    声明式服务调用: Spring Cloud FeignAPI
    网关服务:Spring Cloud Zuul
    分布式配置中心: Spring Cloud Config

    SpringCloud的高级功能:
    : 消息总线:Spring Cloud Bus
    消息驱动的微服务:Spring Cloud Stream
    分布式服务跟踪: Spring Cloud Sleuth

    ]]>
    Spring Cloud系列之微服务介绍 Fri, 20 Jun 2025 02:20:33 +0800 相信我,请认真读完,点开每一个链接,或许你才能真正了解什么是微服务?什么是分布式?什么是云计算?绝对没有多余!

    1 微服务

    微服务架构是“新常态”。构建小型的、自包含的、随时可以运行的应用程序可以为代码带来极大的灵活性和灵活性。spring boot的许多专门构建的特性使得在大规模生产中构建和运行微服务变得非常容易。别忘了,没有spring cloud,就没有一个微服务架构是完整的,它简化了管理,增强了您的容错能力。
    diagram-microservices-88e01c7d34c688cb49556435c130d352.svg

    1.1 什么是微服务?

    微服务是一种现代的软件方法,应用程序代码可以独立于其他程序以可管理的小块形式交付。

    1.2 为什么要构建微服务?

    它们的小规模和相对隔离可以带来许多额外的好处,例如更容易维护、提高生产力、更大的容错能力、更好的业务协调等等。

    2 Spring Cloud

    开发分布式系统可能具有挑战性。复杂性从应用层转移到网络层,要求服务之间进行更大的交互。使您的代码成为“本机云”意味着要处理12个因素的问题,例如外部配置、无状态状态、日志记录和连接到支持服务。spring cloud项目套件包含许多使应用程序在云中运行所需的服务。
    cloud-diagram-1a4cad7294b4452864b5ff57175dd983.svg

    2.1 Service discovery——服务发现

    在云中,应用程序不可能总是知道其他服务的确切位置。服务注册中心(如Netflix Eureka)或sidecar解决方案(如HashiCorp consur)都可以提供帮助。spring cloud为流行的注册中心提供了Discovery Client实现,比如Eureka、consur、Zookeeper,甚至Kubernetes的内置系统。还有一个spring cloud负载平衡器(Spring Cloud LoadBalancer),可以帮助您在服务实例之间小心地分配负载。

    2.2 API gateway——API 网关

    由于有如此多的客户机和服务器,在您的云架构中包含一个API网关通常是很有帮助的。网关可以负责保护和路由消息、隐藏服务、限制负载以及许多其他有用的事情。spring cloud gateway为您提供了对API层的精确控制,集成了spring cloud服务发现和客户端负载平衡解决方案,以简化配置和维护。

    2.3 Cloud configuration——云配置

    在云中,配置不能简单地嵌入到应用程序中。配置必须足够灵活,以应对多个应用程序、环境和服务实例,并在不停机的情况下处理动态变化。spring cloud config旨在减轻这些负担,并提供与Git等版本控制系统的集成,以帮助您确保配置的安全。

    2.4 Circuit breakers——断路器

    分布式系统可能不可靠。请求可能会遇到超时或完全失败。断路器可以帮助缓解这些问题,spring cloud断路器为您提供了三种流行的选择:Resilience4J、Sentinel或Hystrix。

    2.5 Tracing——追踪

    调试分布式应用程序可能很复杂,而且需要很长时间。对于任何给定的失败,您可能需要将来自多个独立服务的信息跟踪拼凑在一起。spring cloud sleuth可以以一种可预测和可重复的方式为您的应用程序安装工具。当与Zipkin结合使用时,您可以将注意力集中在任何可能存在的延迟问题上。

    2.6 Testing——测试

    在云计算中,拥有可靠、可信、稳定的api可以获得额外的分数,但要实现这一目标还需要一段旅程。基于契约的测试是高绩效团队经常使用的一种技术。它有助于规范化api的内容,并围绕它们构建测试,以确保代码始终处于检查状态。
    spring cloud contract通过使用Groovy、Java或Kotlin编写的契约,为REST和基于消息传递的api提供基于契约的测试支持。

    3 流数据

    spring cloud stream使得使用和生成事件变得非常容易,无论您选择哪个消息传递平台。spring cloud stream只需几行代码就可以将您的微服务与实时消息传递连接起来,以帮助您构建高度可伸缩、事件驱动的系统。
    Get started with Spring Cloud Stream

    4 管理微服务

    spring boot的可选仪器框架microller直接将度量发送给Prometheus、Atlas等,以提供有价值的见解。spring cloud的Sleuth和Zipkin项目补充了这一点,它们提供了分布式跟踪,以便您能够实时跟踪正在发生的事情。
    Get started with Micrometer on Spring Boot

    5 云计算

    微服务的小型、无状态特性使其成为水平扩展的理想选择。像TAS和PKS这样的平台可以提供可扩展的基础设施来匹配,并大大减少您的管理开销。使用云连接器,您还可以轻松地使用多个后端服务。
    Try Pivotal Web Services for free

    6 链接

    Spring Cloud系列

    Spring Cloud系列之微服务介绍
    [Spring Cloud系列之微服务注册中心]()
    [Spring Cloud系列之微服务统一配置中心]()
    [Spring Cloud系列之微服务RPC]()
    [Spring Cloud系列之微服务服务监控]()
    [Spring Cloud系列之微服务链路跟踪]()
    [Spring Cloud系列之微服务授权与认证]()

    关于

    我是冯文议(Erwin Feng),Java Developer,专注于程序设计与开发。开源项目:JavaLib、api-result。喜欢电影、游戏、音乐、茶、旅行。
    我的个人网站:https://fengwenyi.com
    我的Github:https://github.com/fengwenyi

    ]]>
    云原生语境下,如何重新解读微服务? Fri, 20 Jun 2025 02:20:33 +0800 头图.png

    最近,O’Reilly 公布了一份关于企业微服务市场现状的数据调研。报告显示,在访问了全球 1,502 名软件工程师、系统和技术架构师、工程师以及决策者后,有 77% 的组织反馈采用了微服务,其中 92% 的组织成功使用了微服务。

    1.png

    如果以这份报告为依据,微服务在企业的普及率已接近八成。看起来,企业对微服务的兴趣可能已经接近顶峰。云原生的基础设施从设计上保证了它是微服务部署的最佳平台,但是也对现有的微服务框架带来了新的挑战,在云原生大行其道的今天:

    • 我们对微服务还应该继续投入精力关注吗?
    • 云原生和微服务之间的关系是什么?
    • 随着 Serviece Mesh 等技术的不断成熟,微服务的体系和思想会产生怎样的演化?
    • Spring Cloud、Dubbo 还会继续作为微服务开发框架的继续流行下去吗?
    • 容器、Kubernetes、ServiceMesh、Serverless 这些云原生时代的主角,会如何助力下一代微服务架构为业务发展赋能?

    这些问题值得每一位技术从业人员去思考,并发现由此带来的企业数字化转型升级新挑战、新机遇。也许有同学会说:“上个阶段微服务架构的问题都还没解决,又来了个‘云原生时代的微服务’,我这从哪儿开始学起啊?”

    来,从这儿开始!

    2.png

    2020 云原生微服务大会

    为推动云原生下的微服务技术发展和实践交流,由阿里云主办的首届“云原生微服务大会”将于 2020 年 8 月 18-19 日在线上召开。本次大会聚焦微服务架构前沿发展和业界最佳实践,重点探讨云原生语境下微服务的挑战和技术趋势,帮助企业技术决策者、架构师、开发者们迎接云原生时代的到来。

    点击活动官网预约大会直播:https://developer.aliyun.com/topic/microservices2020#/

    25 位全球专家共同解读云原生语境下的微服务定义

    我们一直在强调微服务带来的好处,但另一方面,随着业务规模越来越大,拆分的服务实例越来越多,传统的微服务架构中关于服务之间的交互,服务发现、监控、容错性、日志收集和服务熔断等的处理也越来越困难。今天,以容器、服务网格、微服务、Serverless 为代表的云原生技术,带来一种全新的方式来构建应用,也使这些挑战有了可解的办法。

    3.png
    2020 云原生微服务大会嘉宾(部分)

    8 月 18 日 - 19 日的 2020 云原生微服务大会,我们将特邀微软云首席软件工程师白海石,前Red Hat首席架构师、istio in action 作者、solo.io Field CTO Christian Posta,Spring 布道师 Josh Long,阿里云资深技术专家 & CNCF TOC 李响,南京大学软件工程教授 & 微服务方向专家张贺等 25 位全球微服务领域先行者和权威技术专家,深度探讨微服务架构在云原生时代的发展趋势、业界最佳实践和创新应用案例,一定会让你转变思维,重新审视微服务的思想、核心技术和落地路径。

    5 大专场聚焦下一代微服务核心技术和实践

    主论坛:08/18 09:00-12:00

    云原生语境下,微服务也被赋予了新的意义,支持新的应用范式,承载新的计算价值。主论坛邀请多位技术领袖深度探讨云原生趋势下,微服务技术的实践和演进方向。

    微服务开源专场:08/18 14:00-16:30

    在微服务架构的落地和演进的过程中,微服务开源项目日益繁荣并不断赋能开发者。本论坛将聚焦微服务领域热门开源技术的落地实践,与开发者探讨微服务架构开源发展及未来趋势。

    云原生架构专场:08/18 16:30-19:00

    云时代下,企业需要新技术架构,使之更好地利用云计算优势,让业务更敏捷、成本更低、可伸缩性更强,而云原生架构的应用意义正在于此。本论坛将专注探讨云原生架构落地时面临的挑战与解决方案。

    前端全栈专场:08/19 14:00-16:30

    伴随着云+端、Serverless 等技术的发展,势必给前端带来更大的场景与机会,前端即将进入黄金时代。本专场将邀请行业专家深入探讨前端全栈实践问题及趋势。

    超大规模实践专场:08/19 16:30-19:00

    当前云原生微服务化落地的场景多样,行业已有很多生产环境下的实战案例,本专场将邀请来自掌门教育、爱奇艺、携程、中国工商银行的行业专家,在云原生微服务视角下探讨超大规模场景下的实践经验。

    点击链接:https://developer.aliyun.com/topic/microservices2020#/,登录活动官网 ,即可预约 2020 云原生微服务大会在线直播,查看完整精彩内容,还有更多有奖互动环节等你参与!

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    解读Knative 0.16.0版本特性 Fri, 20 Jun 2025 02:20:33 +0800 前言

    Knative 0.16.0 版本已于近期发布,针对 Knative v0.16.0 版本对这些新功能特性进行解读,让你快速对新版本特性有所深入了解。

    从Knative 0.16.0开始,k8s 最小支持版本为:1.16。

    Serving

    Autoscaling-自动扩缩容

    优化缩容时对不可达 revision 处理

    当revision reachable时,没有必要再采用步长缩容对逻辑,直接缩容到0即可。

    移除 PodAutoscaler custom metrics API

    custom metrics API的之前主要作用是提供给 HPA 用于支持基于请求的并发数的metric指标,但是安装custom metrics API 会对 k8s 侵入性太强,另外custom metrics API 对于apiserver的代码维护也带来了问题。针对这些问题,从0.16.0版本开始不再支持custom metrics API。

    核心 API

    多容器支持

    社区终于在0.16.0开始进行多容器支持(尽管当前是alpha)。可以在config-features 配置文件中通过设置“multi-container” 为 “enabled” ,开启多容器特性。

    环境变量支持downwards API

    可以在config-features 配置文件中通过设置“kubernetes.podspec-fieldref” 为 “enabled” ,开启环境变量支持downwards API 。

    webhooks 高可用支持

    通过leader选举支持webhooks主备高可用。

    Networking-网络

    支持通过Header中Tag进行路由选择

    在服务请求的Header中,可以通过指定tag,来访问tag对应的revision版本。
    例如:如果请求访问 http://svc-name.ns.svc 服务,并且在Header中设置了:Knative-Serving-Tag:tag-name, 那么该请求就会路由到http://tag-name-svc-name.ns.svc 对应的revision 服务。

    knative-serving namespace删除istio注入标签

    当前knative-serving namespace的istio注入标签已经没有实际的应用,因此在0.16.0版本中从 knative-serving namespace中移除istio-injection=enabled标签

    Eventing

    V1版本支持

    从0.16.0开始,以下资源提供了v1版本支持:

    • Subscription
    • Channel
    • InMemoryChannel
    • Broker
    • Trigger
    • Sequence
    • Parallel

    这意味着Event这部分功能日渐成熟。

    新增sugar-controller

    引入一个 sugar-controller 主要用于调和 Namespace 和 Trigger,同时会从核心controller中移除Namespace reconciler和 Trigger reconciler

    PingSource 事件接收高可用支持

    当前接收PingSource adapter通过leader选举支持主备高可用部署。

    总结

    随着Knative 0.16.0 版本的发布,社区越来越关注用户 Knative 实际使用中的诉求, 如多容器支持、通过Header中Tag进行路由选择等。相信在后续的版本中会提供更多实用的功能来满足不同的 Serverless 场景实际运用。欢迎有兴趣的同学一起交流。

    欢迎加入 Knative 交流群

    image.png

    ]]>
    十年中间件最佳实践集锦,八月福利大放送 Fri, 20 Jun 2025 02:20:33 +0800 轻松应对业务增长,阿里云为在线教育“定制”丰富的中间件,助力提升系统稳定性、灵活性和反脆弱性,通过消息队列,让你的业务沟通无畅,信息不丢失,还有全年优惠大放送!
    活动链接:https://www.aliyun.com/activity/daily/aioe?spm=5176.12825654.a9ylfrljh.d114.e9392c4aCTHfz0&scm=20140722.2478.2.2461
    9C1BFD7E-DA5B-48A1-B10B-DC1DD961C626.png

    ]]>
    高德地图 AMAP-TECH 算法大赛火热进行中······ Fri, 20 Jun 2025 02:20:33 +0800 image.png

    阿里巴巴高德地图 AMAP-TECH 算法大赛于 7 月 8 日开启初赛,赛题为「基于车载视频图像的动态路况分析」,活动邀请了业界权威专家担任评委,优秀选手不仅可以瓜分丰厚的奖金,领取荣誉证书,还有机会进入高德地图【终面通道】!赶紧邀请小伙伴一起来参赛吧。

    报名请戳:

    https://tianchi.aliyun.com/competition/entrance/531809/introduction

    下面给大家介绍下大赛详情。

    背景

    高德地图每天会为用户提供海量的定位和路线导航规划服务,其所提供的路况状态信息(即道路交通是拥挤、缓行还是畅通)的准确性会影响到用户在出行过程中的决策和体验。传统的路况状态主要依靠驾车用户的轨迹信息生成。在用户少、驾驶行为异常的道路上,这种方法难以保证路况状态的准确性。

    车载视频图像包含了更多的信息量,给了我们另外一个解决问题的视角。通过视频或图片,可以观察到路面的真实状态,包括机动车数量、道路宽度和空旷度等等。基于车载视频图像可以获取更准确的路况状态,为用户出行提供更高质量的服务。

    本赛题要求参赛者通过计算机视觉等人工智能算法,基于视频图像中识别到的路面信息来判断道路通行状态,提高道路路况状态判断的准确性,从而提升高德地图用户的出行体验。

    赛题描述及数据说明

    术语说明

    路况:根据道路的平均车速、道路等级,对道路上车辆通行状态的描述,分为畅通、缓行、拥堵三个等级,在高德地图上显示为绿色、黄色、红色,如图1所示。

    参考帧:存在图像序列内路况状态渐变的情况,每个图像序列存在一幅参考帧图像,参考帧时刻的路况状态为该图像序列的真值路况状态。

    image.png

    图1. 路况状态示意图

    问题定义

    输入:给定一组含有 GPS 时间的图像序列(包含 3-5 帧图像),其中一幅图像作为参考帧。

    输出:以参考帧为准,输出该图像序列对应的路况状态(畅通、缓行和拥堵)。

    如下图所示,该序列包含 3 帧图像,其中第 3 帧图像为参考帧。算法需要基于整个序列,推断路况状态。当图像序列内的路况状态不一致时,以参考帧为准。

    image.png

    图2. 路况判断流程示意

    图像序列由行车记录仪拍摄,路况真值(ground truth)是对应道路当前时刻真实的路况状态。大部分场景下,前方车辆的数量和密度决定了路况状态,但是也存在一些其他情况供参赛选手参考:

    • 行驶道路存在大量路边停车,但不影响车辆行驶,实际路况状态为畅通。
    • 跟车距离较近,前车遮挡视野内道路的情况,影响对当前路况的判断。
    • 行驶在双向道路,对向车道拥堵,行驶车道路况状态为缓行或畅通。
    • 相机安装存在角度偏差,可能会影响路况判断。

    数据说明

    数据集分为预赛数据集和复赛数据集,预赛数据集先公布,仅供预赛使用。复赛数据集等预赛结束后公布,复赛中也可以使用预赛数据集。预赛数据集包括训练集和测试集。训练集合有 1500 个序列、共约 7000 幅图像;测试集合为 600 个序列、共约 2800 幅图像。预赛数据集包含不同等级的道路,包含高速路、城市快速路、普通道路等。路况真值类型的分布情况约为畅通 70% 、缓行 10% 、拥堵 20% 。路况真值主要基于参考帧标注。路面车辆较多的情况下,在标注时结合了序列中其他帧的信息。

    数据格式

    数据组织是以图像序列为单位,每个文件夹包含参考帧和其前后相邻帧的图像序列,图像序列最多为 5 帧图像。

    提供数据中包含以下信息:

    • 图像序列的参考帧图像名。
    • 图像序列的路况状态。
    • 0:畅通,1:缓行,2:拥堵,-1:测试集真值未给出。
    • 每帧图像采集时刻的 GPS 时间。
    • 单位为秒。如 GPS 时间 1552806926 比 1552806921 滞后 5 秒钟。

    提供 JSON 格式标注文件,具体数据格式示意如下:

    "status": 0 畅通,1 缓行,2 拥堵,-1 未知(测试集默认状态为-1)
    {

    "annotations": [
        {
            "id": "000001",
            "key_frame": "2.jpg",
            "status": 0,
            "frames": [
                {
                    "frame_name": "1.jpg",
                    "gps_time": 1552806921
                },
                {
                    "frame_name": "2.jpg",
                    "gps_time": 1552806926
                },
                {
                    "frame_name": "3.jpg",
                    "gps_time": 1552806931
                },
                {
                    "frame_name": "4.jpg",
                    "gps_time": 1552806936
                }
            ]
        },
        {
            "id": "000002",
            "key_frame": "3.jpg",
            "status": 2,
            "frames": [
                {
                    "frame_name": "1.jpg",
                    "gps_time": 1555300555
                },
                {
                    "frame_name": "2.jpg",
                    "gps_time": 1555300560
                },
                {
                    "frame_name": "3.jpg",
                    "gps_time": 1555300565
                },
                {
                    "frame_name": "4.jpg",
                    "gps_time": 1555300570
                },
                {
                    "frame_name": "5.jpg",
                    "gps_time": 1555300580
                }
            ]
        }
    ]

    }

    评价方式

    路况包含通畅/缓行/拥堵三种状态,比赛评分考量每个图像序列的路况分类的准确情况,采用加权 F1 Score 作为算法评价指标。

    image.png

    image.png
    image.png

    权威专家评委团

    为体现比赛的专业性,我们邀请到了多位权威专家来担任评委,包括:北京大学教授 查红彬、中科院自动化所研究员 王亮、阿里巴巴高德地图技术委员会主席 李小龙(聪云)、阿里巴巴高德地图首席科学家 任小枫、阿里巴巴达摩院自动驾驶实验室负责人 王刚(永川)。同时,阿里巴巴高级算法专家郝志会担任本次比赛的明星师兄,他们将为参赛团队提供最为专业的指导。

    赛程、参赛对象

    本次大赛分为初赛、复赛及决赛三个阶段:
    报名(7月8日-8月28日,UTC+8)。
    初赛(7月8日-8月31日,UTC+8)。
    复赛(9月4日-10月13日,UTC+8)。
    决赛(10月下旬,具体时间待定,UTC+8)。

    数据集将于 7 月 8 日正式开放下载。7 月 20 日10:00 AM 以后可以在线提交测试集的路况识别结果( JSON 文件格式)进行评测。评估程序根据真值,计算选手得分。

    大赛面向全社会开放,个人、高等院校、科研单位、企业、创客团队等人员均可报名参赛,个人参赛或组队均可,组队人数上限为3人。

    奖项设置

    冠军:1支队伍,奖金6万元人民币+获奖证书。
    亚军:1支队伍,奖金4万元人民币+获奖证书。
    季军:1支队伍,奖金2万元人民币+获奖证书。
    优胜奖:2支队伍,每支队伍奖金1万元人民币+获奖证书。

    复赛审核通过的排名前 10 的队伍将有机会进入阿里巴巴高德地图校招绿色通道。
    (上述奖项以方案评审及线上实战总决赛后的最终名次决定)

    拉上小伙伴来参赛吧!

    参赛者交流

    扫描以下二维码或搜索钉钉群号 31160357 加入,重要节点通知会在群内第一时间告知,如对本次赛事、赛题及赛制有任何疑问,也可在群内@任意管理员提问。

    image.png

    ]]>
    轻松处理高于平常10倍的视频需求,还能节省60%的IT成本,蓝墨做对了什么? Fri, 20 Jun 2025 02:20:33 +0800 zljuutym.jpg
    近年来,Serverless 一直在高速发展,并呈现出越来越大的影响力。主流的云服务商也在不断地丰富云产品体系,提供更好的开发工具,更高效的应用交付流水线,更好的可观测性,更细腻的产品间集成,但一切才刚刚开始。

    国内一些大公司已经有了一些成熟的Serverless应用案例,一些创业公司也开始加入Serverless阵营,如果说Serverless到底解决了什么问题,核心就是节约成本、节省精力。

    蓝墨是一家由美国留学生回国创业的高科技公司,专注于移动互联时代数字出版和移动学习领域的新技术研究及平台运营,依托自主研发的蓝墨移动交互式数字教材核心技术体系,为出版社、学校和教师提供移动交互式数字教材、校园移动学习平台及教师自助数字出版等解决方案。

    自2012年成立以来,蓝墨的业务就一直保持着高速增长,其技术团队也在逐步发展壮大。从创业开始,蓝墨的技术团队对于一些开放的新技术抱有极大的热情,并愿意应用快速迭代的互联网新技术,来确保其业务系统稳定运行。在疫情期间,在线教育迎来需求爆发,蓝墨加大了整合业界优质课程资源的力度,不断拓展自身的业务边界,在赢得机遇的同时,技术团队也面临了前所未有的挑战。

    视频处理相关业务是蓝墨技术团队遇到的最棘手的问题之一。蓝墨每天都要处理大量视频教材资源,涉及到视频剪辑、切分、组合、转码、分辨率调整、客户端适配等一系列复杂的技术工作。在前几年的技术实践中,蓝墨技术团队通过FFmpeg等技术已经建立起一整套自主可控视频处理机制,支撑了业务的快速发展。但今年的业务增长速度是蓝墨的工程师们始料未及的,高峰期数十倍于往年的视频处理需求让现有的架构不堪重负,严重影响了用户体验。

    怎么办?蓝墨技术团队的第一个想法是扩容!

    水平扩容是能够立竿见影解决问题的,但在全天范围内,视频处理的需求量存在极为陡峭的波峰波谷,大量的任务发生在几个高峰时间段,而且具有一定程度上的突发性。如果按照最高峰期的需求量来规划系统容量,会造成计算资源的大量浪费,而且最高峰期的需求量到底是多少,也是很难预判的。如果采用错峰处理的方式,可以降低计算资源成本,但对于用户的需求得不到及时的处理。

    水平扩容方案因此被放弃。

    如果水平扩容走不通,能不能从视频处理的角度提升效率。比如使用云厂商提供的视频转码服务,类似于BaaS的方式。这样的方案可以完全不用考虑容量规划的问题,根据实际业务量按需调用云服务即可。看似可行,但是结合蓝墨自身业务的实际情况,技术团队又陷入了犹豫。蓝墨的视频处理机制做得非常精细,这里面包含着大量自定义的业务逻辑,需要使用代码来实现,而这些功能是第三方的视频转码服务根本无法实现的。

    推倒重来!蓝墨现在的核心诉求概括有三个:节省成本、极致弹性、免运维,而这些恰恰是Serverless最擅长解决的问题。经过对国内云厂商提供的Serverless服务的多方面调研后,蓝墨技术团队一致认为在视频处理领域阿里云函数计算是最适合他们的方案。

    函数计算(Function Compute,简称FC)是一个事件驱动的全托管 Serverless 计算服务,对于使用者而言,无需管理服务器等基础设施,只需编写代码并上传,函数计算会准备好计算资源,并以弹性、可靠的方式运行代码。由于FC完全兼容现有的代码逻辑,也能够支持各类主流的开发语言,所以蓝墨技术团队可以把代码逻辑以近乎无缝衔接的方式从原有的架构迁移到FC上,并且成本极低。通过对接OSS触发器,只要OSS上有新的视频源文件上传,就能自动拉起函数计算实例,开启一次视频处理业务的生命周期。通过整合Serverless工作流,还能对分布式任务进行统一编排,实现对于大文件切片后进行并行处理并最终合并的复杂操作,更进一步的提升了处理效率。

    对于蓝墨的技术团队而言,函数计算是一种完全按需调用的Serverless化方案,能够充分利用云计算的弹性能力。一方面,函数计算FC能够在短时间内迅速调集上万个实例的计算资源,实现视频处理任务的快速执行;另一方面,由于不需要预留计算资源,也不需要对底层的软硬件进行维护,可以极大地降低运营成本,让蓝墨技术团队更专注于复杂业务逻辑的实现上。相比于传统的方式,基于函数计算FC的Serverless方案在视频处理场景下,可以帮助蓝墨节省了60%左右的IT成本投入。
    image.png
    Serverless的价值不言而喻。阿里云是国内最早一批推出Serverless计算服务的企业,函数计算(Function as a Service)更是 Serverless 中最具代表性的产品形态。可以说,蓝墨并不是第一家享受到函数计算FC巨大价值的企业,新浪微博、石墨文档、芒果TV等都是函数计算的拥趸者。前不久,在2020可信云线上峰会上,阿里云函数计算FC通过了基础能力要求、平台可观测能力、服务性能、服务和服务计量准确等21项测试,以全部满分的成绩通过了可信云函数即服务能力认证。此前,在Forrester发布的报告中,阿里云函数计算被给予“强劲表现者”的评价。

    除了拓展更加丰富的产品形态,阿里云函数计算也不断优化用户体验,包括做了硬盘挂载、预留实例、镜像加速、大规模实例等业内领先的实践,真正把用户需求放在首位,沉下心来做对用户更有价值的产品。

    所以,蓝墨才会坚定地选择函数计算,并在视频处理之外的其他业务领域,积极探索可以进行Serverless化改造的场景,成功落地了FC和日志服务的集成。当日志以流的方式源源不断写入时,日志服务会自动触发函数计算FC对数据进行处理,分析日志中的重要信息,实现异常事件的报警,并按照业务规则把日志进行压缩、转换后存放到其他媒介中,从而更好地保障系统稳定高效运行。

    蓝墨技术团队负责人表示,将来蓝墨会将更多场景与函数计算FC进行整合,充分享受云原生的技术红利,提升整个团队的战斗力。而阿里云也将一直与用户站在一起,打赢接下来每一场漂亮的战斗!

    【加入阿里云在线教育客户交流钉钉群】

    欢迎扫码加入在线教育行业客户交流钉钉群,阿里巴巴众多专家将在群内定期分享行业最佳实践和前沿技术干货,扫码入群,与更多行业精英互动交流。扫码或搜索钉钉群号均可加入:35712134。

    test

    【填问卷抽淘公仔-阿里云中间件用户调研】

    点击链接,一分钟填问卷抽淘公仔:
    https://survey.aliyun.com/apps/zhiliao/YmW95Gk8bU

    【更多干货】:
    1、点击链接了解更多在线教育案例、最佳实践、优惠活动:
    https://www.aliyun.com/activity/daily/aioe
    2、点击 阿里云函数计算 了解更多实践案例。

    ]]>
    金蝶天燕加入阿里云原生合作伙伴计划并发布金融行业服务治理联合解决方案 Fri, 20 Jun 2025 02:20:33 +0800 huagai_VCG21gic19202788_RF_2M.JPG
    近日,金蝶天燕与阿里云正式签订“云原生合作伙伴计划协议”并联合推出金融行业服务治理解决方案,提升数字化运营效率。对此,阿里云云原生生态负责人宁晓民说:“金蝶天燕是中间件行业的领导厂商,是阿里云在金融行业的重要合作伙伴,此次合作后,双方将自身优势强强联合,共同推动金融行业数字化转型。

    数字化时代,微服务、分布式结构逐渐成为主流,云原生已成为云计算的技术趋势。阿里云在云原生领域的投入广泛而深入,在容器、服务网格和 Serverless 等领域均有丰富的技术和产品体系。然而随着业务发展,IT服务越来越多,如何协调各种服务,保障服务的SLA(服务级别协议,保障服务品质、水准、性能),对服务架构和运维人员是一个很大的挑战。为了满足服务线下管控、保障线上高效运行,需要有一个统一的服务治理平台对服务进行统一、有效管控,保障服务的高效、健康运行。

    在金融行业,对服务治理的需求更加迫切。现有大而全的系统犹如一张大网,错综复杂,体现为“三不清晰”和“三多”。
    “三不清晰”:系统边界不清晰;系统部署架构不清晰;功能架构不清晰;
    “三多”:涉及到的人员角色多;业务流程多;服务环节多。
    总之,牵一发而动全身,所以,对服务治理有着更高的要求。

    “金蝶天燕阿里云原生金融行业服务治理联合解决方案”面向全领域金融业务系统,帮助金融企业梳理关键业务流程。如银行IT系统业务需求流程梳理、系统接口与交易流程梳理、微服务架构下的服务化和标准化治理等,实现覆盖服务全生命周期的统一管理。为金融行业提供高性能、高可靠兼具扩展性与安全性的服务治理平台。

    在中国,云计算的整体渗透率仍不足 10%,金融领域更是远低于这个数字。阿里云作为国内顶尖的云服务商,有着成熟的架构体系与稳定的平台。金蝶天燕的产品曾服务于中国人民银行、中国证券监督管理委员会、中国银行、光大银行等金融行业的重要客户,在金融行业有着丰富的服务经验。

    金蝶天燕与阿里云一直是关系紧密的合作伙伴,在去年推出了“政府数字新财联合解决方案”,得到了广泛好评。此次“金融行业服务治理联合解决方案”是双方在金融领域迈出的新一步,未来双方将继续深入合作,金蝶天燕将发挥自身优势,立足阿里云平台,提供更多的优秀产品与解决方案。

    点击:阿里云原生合作伙伴计划,加入我们。

    ]]>
    【升级】8月5日阿里云服务热线95187维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    尊敬的阿里云用户,您好

    接北京移动运营商通知,将于2020年8月5日00:00-01:00期间,对阿里云服务热线95187进行线路升级切换。
    如您使用的是移动手机号码,此段时间将无法正常拨打95187,建议您拨打阿里云售后热线4008013260进行服务咨询。或使用联通、电信号码拨打95187进行咨询。
    感谢您的理解与支持,谢谢。

    ]]>
    【升级】8月13日DDoS高防(新BGP)升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【DDoS高防(新BGP)】【升级通知】

    升级窗口:北京时间 2020年8月13日 02:00- 06:00

    升级内容:DDoS高防(新BGP)进行网络升级操作。

    升级影响:升级期间,部分IP需要重新连接,会导致TCP连接闪断2-3次。闪断对短连接和具备自动重连的长连接业务基本无影响,请确保您在业务上做好重连重试机制,以增强业务的容错能力。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【漏洞预警】Nexus Repository Manager 3.x 远程代码执行漏洞(CVE-2020-15871) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月3日,阿里云应急响应中心监测到Nexus Repository Manager 3.x 版本存在远程代码执行漏洞。


    漏洞描述

    Sonatype Nexus Repository 是一个开源的仓库管理系统,在安装、配置、使用简单的基础上提供了更加丰富的功能。近日Sonatype官方发布安全公告披露了在Nexus Repository Manager 3.x 版本中存在远程代码执行漏洞(CVE-2020-15871),攻击者可在登录后利用该漏洞执行任意命令。漏洞利用需要登录,危害相对较小。阿里云应急响应中心提醒Nexus Repository Manager 3.x 用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    Nexus Repository Manager OSS/Pro version < 3.25.1


    安全建议

    升级到Nexus Repository Manager 3.x 至最新版本 3.25.1


    相关链接

    https://support.sonatype.com/hc/en-us/articles/360052192693-CVE-2020-15871-Nexus-Repository-Manager-3-Remote-Code-Execution-2020-07-29



    阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.8.3

    ]]>
    菜鸟+Hologres=智能物流-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者:阿里巴巴菜鸟物流团队(弃疾,孝江,姜继忠)

    一、业务背景

    菜鸟智能物流分析引擎是基于搜索架构建设的物流查询平台,日均处理包裹事件几十亿,承载了菜鸟物流数据的大部分处理任务。
    智能物流分析引擎将基于运配网络的各类应用场景集中到了统一的一个技术架构,以此提供强大的吞吐和计算能力。基于原架构的数据处理流程为:Datahub实时采集数据源,包含仓、配、运和订单等数据,实时计算Flink基于流批一体的模式对数据预处理,形成一个以订单为单位,包含订单跟踪事件的宽表,写入存储引擎HBase中,再供外部查询。

    在数据处理部分,随着数据量的增加,原有的存储系统HBase在维表全量导入中所需要的时间越来越长,这就需要耗费大量的资源,另外其单机吞吐的表现不是很好,单位成本高。在数据量较小时,成本不是需要考虑的关键因素,但当数据量规模变大时,成本的重要性就体现出来了。菜鸟智能物流每天需要处理大批量的数据,这也就意味着每天将会浪费大量的资源。

    同时,在我们的场景中,有些表是作为Flink维表基于PK进行PointQuery,有些表需要进行OLAP分析,而HBase并不能两种场景都满足。为了OLAP分析,需要将数据同步到批处理系统中,为了KV查询,需要将数据同步到KVStore。不同的查询需求就需要借助多个系统,数据在不同系统之间的导入导出不仅会加深数据同步的负担,也会带来冗余存储,也极容易出现数据不一致的情况,并且多个系统也会给开发和运维带来一定的成本。

    基于以上背景,当前我们最需要解决的问题是降低整体的资源消耗成本,那么就需要有一款产品既能提供存储能力还要提供高性能的写入能力。而在查询场景上,若是这款产品能同时满足KV查询和复杂OLAP查询将会是加分项,这样就会解决多个系统带来的数据孤岛问题,一次性满足所有需求。

    我们在集团内对多个产品进行了调研,最终选择了Hologres替换现有的HBase。

    二、业务架构

    菜鸟物流引擎需要处理大量的表和数据,全量任务快递线和仓配线通过MaxCompute(原ODPS)表的日分区快照做驱动源,增量任务通过对应的事件流做驱动,来进行引擎数据写入。
    全量任务会根据包裹的历史履行进度进行聚合,生成这个包裹的客观履行和历史属性信息,并通过Flink Job实时同步更新到Hologres里,提供给数据任务进行关联。实时数据在接收到一条事件消息后,首先会去关联这条包裹历史履行,并会调用算法服务链,进行拆合单、末端网点预测、路由选择、时效预测等,生成新的预测履行进度。新的预测履行会作为回流数据写入TT(消息中间件,类似Kafka)和Hologres中,并再提供给数据任务进行关联。
    通过数据任务之间的互相协同,我们对数据关系进行了梳理,并尽量降低数据之间的依赖,最终业务处理架构如下图所示:

    • 数据驱动层 在数据驱动层中,包含几个部分:全量任务的主表驱动、增量任务的主表驱动、业务辅表的驱动。
    • 数据关联层 数据关联层主要包括各种Flink的SQL Operator。为了提升全量任务和增量任务的吞吐,通过存储和计算优化,将数据关联尽可能的分布到不同的数据分区上,来进行性能提升。
    • 数据交互层 索引数据通过Swift Sink的方式写入到索引构建服务中;要持久化的内部数据,通过写入接口保存到存储服务中。

    image.png
     

    三、业务价值

    将HBase替换成Hologres之后,给业务带来的价值主要有以下几个方面:

    1.整体硬件资源成本下降60%+
    对比HBase,相同配置的Hologres有着更强的写入性能,能够提供更好的吞吐量,也就是说我们可以用更少的资源来满足现有数据规模的处理需求。在实际业务应用中,整体硬件资源成本下降60%+,解决了我们最棘手的问题。

    2.更快的全链路处理速度(2亿记录端到端3分钟)
    全量数据处理所需的时间是非常重要的指标,设想某一天新发布的数据处理代码有bug,新产出的数据不可用,即使修复了代码,还得继续解决已经存在的错误数据,此时就要跑一次全量,用正常的数据覆盖错误的数据。全量任务的运行时间决定了故障的持续时间,全量运行的速度越快,故障才能越快解决。
    在物流分析引擎的全量中,我们需要先通过所有维表的数据,确保维表自身的数据是正确的,这是一个非常耗时的操作。以其中一张表为例,2亿多的数据量,使用Hologres同步只需要3分钟左右,这也意味着可以更快的执行完毕全量数据,以便我们能够更从容应对突发情况。

    3.一个系统,满KV和OLAP两个场景,没有数据冗余
    Hologres在存储上支持行存和列存两种存储模式。列存适合海量数据的交互式分析,而行存适合基于Primary Key的整行读取。这就意味着我们可以将所有的数据存储在Hologres中,需要PointQuery就选择行存模式,需要复杂OLAP分析就选择列存模式,满足了OLAP和KV查询,无需再借助其他系统,既保证了数据存储的唯一性,也避免了各种系统之间的导入导出和复杂运维。

    4.大维表实时SQL查询
    以前如果想查一下维表中的数据,由于是KV接口,并不是很方便。Hologres兼容PostgreSQL生态,可以直接使用psql客户端访问,通过标准的PostgreSQL语法查询表中的数据,支持各种过滤条件,能够很方便的实时检查数据是不是有问题。

    5.强Schema
    原有的维表存储是一个弱Schema的存储服务,在Flink任务中,即使访问不存在的字段也不会报错,只是获取到的字段值为空。代码里不小心写错了字段名,一是很难立刻发现,通常要等到数据产出时候才能发现,甚至只能等用户发现,另外排查起来也很麻烦,没法直接定位。使用Hologres的时候字段名写错立即报错,错误信息很明确,避免了潜在的错误风险,还能节省时间。

    ]]>
    关于小机 | 计算机百年趣味史(上)第8篇-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 上一篇:七个”小矮人” | 计算机百年趣味史(上)第7篇
    下一篇:PC时代 | 计算机百年趣味史(上)第9-10篇

    本文作者:衍云

    小机即小型机(minicomputer),从名字上我们可以知道是体积会较小的机器,不过体积也是针对大机(mainframe)来说是,如果光从绝对体积上讲,那显然又不对。所以,小机是对特定时代一群类似机器的统称。我们来看下小机的关键历史。其历史时间是与大型机并行的。

    1950年,肯·奥尔森(小型机之父)在MIT攻读硕士研究生的时候就加入了SAGE(Semi-Automatic Ground Environment,半自动地面环境,也就是上面提到的SAGE)项目中,后来和IBM的合作中,他看到IBM内部的官僚等级,奥尔森深感不满,并决定打败他们。

    于是在1957年,DEC成立了。通过存储测试逻辑软件和存储测试器站稳后便开始向计算机研制进军。1959年12月,DEC公司向市场推出了它的第一台计算机PDP-1的样机。

    1964年,推出了小巧玲珑的PDP-7型计算机(18位机),首次使用了倒装芯片(Flip chip),是的这就是UNIX最早诞生的元老。

    1965年的秋季,DEC公司推出了小巧玲珑的PDP-8型计算机(12位机),销售迅速扩大,抢占了IBM公司的计算机市场。

    1970年1月,DEC推出了PDP-11型计算机(肯·汤普森Ken Thompson跑的第一版Unix)。PDP-11拥有一系列计算功能,很快成为小型计算机工业的榜样,同时成为了小型计算机设计的楷模,成为了巅峰。

    不过PDP系列相互之间不兼容,导致每次程序需要重新移植。

    对于这些小巧玲珑的小机,IBM真是等到1979年时候,才开始意识到,并制作中小型商用计算机系统(System/3, System/32, System/34, System/36),当1988年时候IBM推出了下一代就是AS/400系列在与各个厂家竞争中独占鳌头,形势才发生变化。DEC也开始划水下坡。

    不过就像IBM忽略了小机市场那样,DEC也忽略了PC市场,使得苹果电脑占领了先机,而当IBM在1981年杀入PC市场,再加上DEC产品失误,后来就彻底没有DEC什么事情了。

    1998年1月DEC公司被竞争公司康柏(Compaq)以96亿美元的价格收购,结束了DEC在历史舞台上的最后一幕。

    和大机一样,只要有需求就会有竞争。小机厂家除了IBM,DEC之外,还有很多厂家,只是相比DEC晚了一些或者说更缺少一种典型。美国的Sun(1982年成立,早期使用摩托罗拉公司芯片,现已被Oracle收购)、日本Fujitsu(富士通)等公司的小型机是基于SPARC处理器架构(该处理器由1985年Sun公司研制,现在Oracle已放弃了SPARCE转用Intel Xeon),而美国HP公司的则是基于PA-RISC架构,后基于Itanium ,而最新的SuperdomeX也基于Intel Xeon;Compaq公司是Alpha架构。另外,不同厂家的小机其内部的各种总线也会存在差异,例如I/O总线,Fujitsu是PCI,而Sun是SBUS。处理器除了以上架构外,还有MIPS架构由斯坦福(Stanford)大学JohnL. Hennessy校长/教授(和David A. Patterson合著有《计算机体系机构:量化研究的方法》,并与2017年一起获得图灵奖, David A. Patterson 教授2016年,加入Google TPU团队)领导的研究小组1981开始研制。

    此外,由于小机的发展刚好赶上的UNIX的热潮,所以很多小型机都开始使用基于Unix的操作系统,像Sun Solaris(已被Oracle抛弃),HP是用HP-UX,IBM是AIX和OS/400。小型机是封闭专用的计算机系统,用小型机的用户一般是看中安全性、可靠性和专用服务器的高速运算能力。由于UNIX操作系统在小机上的霸主地位,很多人也将小机叫做UNIX服务器。

    小机相比大机市场竞争更加激烈,也更百花齐放。

    国内也有厂商基于Itanium开发小型机,在863国家项目的基础上,浪潮和华为均开发了产品。不过只有浪潮的天梭K-1系统在2013年上市,而华为转而使用Intel Xeon并推出了KunLun服务器。

    最后在小机篇中,

    我们来看下小机中的战斗机,IBM Power系列(集团在2012下线的小机就是IBM的Power系列机器)。

    目前最新的IBM Power机器是Power E980(也是几年前的产品,这个小机的定位已经决定其更行迭代较X86/ARM慢),外观没啥好看的,和普通的X86 4路服务器几乎一样,重要的其内置核心。

    我们来看下他强大的配置,最大16 x POWER9 processors(8, 10, 11 or 12 cores each),也就是128, 160, 176 or 192 Power9核心。这里最大是物理核心192个,开超线程(4thread per core)后是最大得到768线程,最重要的几乎完美的扩展性。

    6.jpg

    Power是标准的SMP结构,对于内存来说所有CPU访问的速度都是一致的,而x86采用了NUMA结构,每个CPU访问自己的这部分内存特别快,但是如果需要访问其它部分那就要走UPI总线,客观上造成了随着CPU数量的增多,处理能力的增长Power系列的线性程度远好于x86。另外,作为小型机,搭上自家的AIS系统,其设计更加完整紧凑,综合起来性能完爆Intel X86是没啥问题的,就是一个问题,贵!不过相比Z15大机,小机还是性价比更高的,毕竟价格摆在那里。

    扯远了......回到历史......

    ]]>
    开源国产数据库峰会-南京站-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 国际形势、国内趋势,现在中国数据库市场暗流涌动,这次盛会,让处于中国数据库一线的专家们为你解惑释疑。

    此次大会由中国计算机学会、开源中国、开源&国产数据库联盟、神脑资讯(CUUG)等单位主办,特邀阿里云、腾讯、迪思杰、亚信科技、苏宁易购、人大金仓、南大通用等企业代表,特邀北京大学、武汉大学、南京财经大学等院校代表,共同探讨国产数据库的发展,为各行各业提供去‘O’的全面解决方案,为现在身为DBA或者即将走上这条路的人士提供指导方向。

    参会嘉宾:

    周正中 (网名:德哥、阿里云数据库专家、PG 中国社区大学校长)

    秦小麟 (南京航空航天大学数据管理与知识工程研究所所长,江苏省政协常委)

    彭煜玮 (武汉大学计算机学院教授,CCF 数据库专委会委员)

    孙国梓 (南京邮电大学教授,CCF 南京副主席)

    韩宏坤 (迪思杰(北京)数据管理技术有限公司总裁)

    张文升 (PG 中国社区核心成员,著有《PostgreSQL 实战》和《PostgreSQL 内幕探索》畅销书籍)

    陈河堆 (中兴通讯数据库平台负责人,PG 中国社区核心成员,著有《深入浅出 PostgreSQL》畅销书籍)

    姜明俊 (亚信科技数据库技术创新实验室总监,PG 中国社区核心成员)

    陈华军 (苏宁易购 IT 总部资深技术经理,PG 中国社区核心成员)

    梁红凤 (北京人大金仓信息技术股份有限公司副总裁)

    杜国旺 (南大通用副总裁,首席战略官)

    陈卫星 (北京神脑资讯技术有限公司(CUUG)总裁)

    刘建国 (北京神脑资讯技术有限公司(CUUG)总经理)

    会议方式:

    线下:阿里云创新中心基地

    报名方式:https://jinshuju.net/f/gvUtP5

    线上:同步直播

    直播地址:https://wx.vzan.com/live/tvchat-778077007

    参会人员奖项:

    一等奖:PG中级认证课程(价值6800元) 5名

    二等奖:PG高级课程(价值5800) 5名

    三等奖:《PostgreSQL 实战》《深入浅出PostgreSQL》作者现场签名 各10本

    四等奖:峰会精美纪念品 30名

    五等奖:峰会纪念T恤 400名

    报名方式、直播地址、主题分享、会议抽奖等内容会在群内公布

    QQ群1:913256657

    ]]>
    什么是DTS | 《DTS控制台入门一本通》第一章-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 下一章:DTS概览页 | 《DTS控制台入门一本通》第二章

    点击免费下载
    《DTS控制台入门一本通》>>>

    test

    也可以PC端点击https://developer.aliyun.com/topic/download?id=803 下载

    DTS 全称为数据传输服务 DTS,它最常见的用途是把本地数据库的数据迁移、同步到阿里云,或者把阿里云的数据迁移、同步到本地。它更多的是一个数据迁移产品,它最终做的事情就是把数据从源端抽取 ( 复制源端数据,源端数据依然还在 ) 然后写入到目标端。另外,它还提供数据订阅的功能(关于数据订阅,后面章节会进行讨论),支持对增量数据进行订阅。简单的可以理解成下图这种架构,如图 1-1。
    image.png

    ]]>
    云上备份-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 讲师简介:
    辛阳,灾备技术国家工程实验室常务副主任,教授。
    吴结生,阿里巴巴高级研究员、阿里云智能存储负责人
    李媛(紫英),阿里云混合云灾备技术专家,阿里云产品经理
    张磊,阿里云智能高级技术专家

    目录

    一、灾备技术(灾备技术国家工程实验室常务副主任辛阳)
    二、云,让灾备更简单(阿里巴巴高级研究员吴结生)
    三、阿里云混合云灾备应用场景实践与案例解读(阿里云智能高级技术专家张磊)

    一、灾备技术

    灾备技术简介:
    习主席指出:“没有信息化就没有现代化,没有网络安全就没有国家安全“。

    image.png

    灾备技术作为”网络安全的最后一公里“,其不仅是网络安全保障体系的重要组成部分,也是网络与系统基础设施持续有效运行的基本保障,是信息化国家战略的重要支撑。

    灾备中心作为数据中心建设的基本组成部分,也是新基建工作的重要组成部分。

    从全球角度看,移动互联网、云计算、大数据、下一代移动通信等信息技术的发展,使得数据量呈爆炸式增长,人工智能、深度学习、类脑计算等数据再利用技术的成熟,又使得数据价值呈指数上升。

    但是火灾、地震、跑水、网络安全黑客攻击、运营误操作等天灾人祸时时刻刻威胁着数据和信息系统的稳定运行,重要数据与信息系统一旦受到破坏等同于企业和组织的"现金流"被追中断。

    如今我国政府业和企业的关键业务系统已经全部信息化,保持业务运行的持续稳定既是重要的也是迫切的。

    灾备行业相关法规:

    image.png

     2017年6月1日《网络安全法》正式实施,三十四条规定"对重要系统和数据库进行容灾备份",从法律角度对信息系统的备份进行了强制要求。

     2019年12月1日《信息安全技术网络安全等级保护基本要求》2.0正式实施,在新的标准中,对于数据备份和灾难恢复做出更高要求:除了备份之外,还要有数据和业务系统的本地高可用和异地容灾手段。

    灾备行业相关标准:

    image.png

    国际标准:目前,通用的暂难恢复标准采用的是1992年的SHARE78标准。

    国家标准:
    1.2007年中国灾难备份与恢复行业的第一个国家标准《信息系统灾难恢复规范》(GB/T20988-2007)开始正式实施。

    2、2013年《公共安全业务连续性管理体系要求》国家标准(GB/T 30146-2013)正式发布。

    3、2019年7月1日由全国信息安全标准化技术委员会(SAC/TC260)提出并归口的《信息安全技术灾难恢复服务能力评估准则》(GB/T 37046-2018),正式实施。

    云灾备的优势:
    云灾备的概念与分类:
     云灾备是指灾备业务的云端实现形式,主要包括云备份与云容灾;
     云备份与云容灾是一个有机的统一体,其中云备份是指备份技术将生产存储数据直接备份到公有云上,进而实现数据备份与恢复功能;
     云容灾则是指通过数据系统的云端迁移、高可用等方式实现业务的快速接管,保证业务连续性;
    优势:

    image.png

     基础设施减少,降低IT成本
     按需付费,高度机动性
     高度灵活性,快速恢复

    云灾备的关键技术:
     重复数据删除技术
     数据压缩传输技术
     云存储与安全隐私技术
     虚拟化与超融合技术
     云数据库备份技术

    云灾备技术未来发展方向
     微服务的支持
     NEW sql数据库支持
     多云和混合云的支持

    云灾备发展中的问题:

    image.png

    用户角度:
    1.普及云灾备与数据安全技术提升灾备安全意识;
    2、云灾备人才培养需要进一步加快;
    3、充分挖掘灾备技术数据的再利用价值,降低总TC0;

    云厂商角度:
    1、灾备技术需要不断突破,尤其对专网;
    2、大力研发数据隐私保护技术;
    3、进一步拓展广义"灾备技术"内涵,进一步降低云灾备成本;
    4、提供可选的多种多选第三方灾备数据安全技术增值应用

    监管角度:
    1、云灾备标准体系进一步完善;
    2、推动行业协会发挥更大的作用;
    3、数据隐私性要求要有强有力的惩罚性条款配套;

    云灾备-大势所趋:
    “上云”和“云上”将成为常态,“云灾备”和“灾备云”大势所趋。
    大力发展以云为代表的灾备基础设施是行业所需也是产业技术发展的必然趋势。

    二、云,让灾备更简单

    唯一不变的是变化:

    image.png

    依靠数据来进行决策和运营,数据的价值化会引导其极致的地位,在这些变化的后面大家可能会去思考它的驱动力是什么,这个驱动力就是所说的数字化经济的转型,是业务的数字化、在线化以及智能化。

    在数字经济时代灾备是最基础的技术需求,灾备技术是保障数据安全和技术安全的基本需求。

    在灾备的构成实际包含两部分:做数据保护,做业务连续性的保障。只有在两种保护下面才能做到不论发生什么故障,系统才能正常运行的。

    ALL IN CLOUD,拐点已至:

    image.png

    云存储逐渐成为主流的寄载点,数据一般分为三种:
    1.存在个人电脑,手机上的个人消费数据。
    2.存在企业数据中心的数据,企业数据中心层数
    3.位于规模云上的数据层数。

    灾备市场洞察:

    image.png

    灾备市场的一个情况,2019年达成了对数据中心的这种灾备的一个调查,就是分析发现,据说到2020年的时候,30%的大型企业会使用这个快照和备份,20%的企业会使用这个语音作为备用库,另外到2022年的时候40%的企业会替换他们在2018年备份的方案。

    这个调查的市场从另一个角度来看,到2021年的时候呢,使用情况会达到281,那么相比2020年或者是2022相比2021的话,往年增长大概是17%左右。

    总结来说:
    第一:灾备技术是保障这个信息安全和数据安全的基础技术需求。

    第二:云存储灾备是大势所趋,就如辛阳教授提到的那样。

    第三:灾备市场的扩大,年增长率会进一步的加速。

    传统灾备解决方案面临的痛点:

    image.png

    围绕公共云的灾备方案解决痛点,典型的两地三中心的方案,在同城的时候做数据中心,做数据双活,在远一点的地方找一个异地容灾的数据中心,就是上述有图绘制所示。

    每个数据中心上部署应用做备份,做容灾。在传统的方案上具备图中的几个痛点。

    云灾备解决方案:

    image.png

    在云灾备的解决方案里,假如客户拥有自己的数据中心,就是左上的生产中心,那么云灾备就可以在同城就近找一个阿里云的数据中心做一个业务的迁移,而阿里云会找一个远程的异地容灾能力进行备份。

    从客户角度来看可以不用去维护多个数据中心,而在公共云上容灾服务是以托管的方式进行的,可以一键部署不需要运维的。

    而在云上有很多优点,可以做资源的弹性伸缩,按时恢复,随时演练,对于随时演练来说,可能拥有一套比较完备的灾备解决方案,但是由于没有很方便的方式来做演练,到真正的用起来的时候会发现业务的需求可能会导致发生很多的损失。

    公共云是解决传统灾备问题的最佳方案:

    image.png

    基于公共云的灾备方案较传统方案,他在客户的痛点上有如下几个特点:第一个特点就是可运维性和可维护性,在传统灾备方案里面因为比较复杂,在公共云的战略方案里面就是全托管免运维服务。

    在扩展性方面它就发挥出这种弹性资源的弹性伸缩这种能力,这种分配是按需计费的。

    在传统解决方案里面扩展系统方面实际上压力还是比较大的,因为它要做一些扩容,扩容一般周期比较长。公共云的拓展上线的速度就是分钟左右,在工作上是分钟级的一个部署,即开即用。

    那么在这个在传统的解决方案里面,若要建造这些数据中心能够进入到这个设备进入这个网络,连通性需要几个月或者以年的这种长度来建造,然后从成本的角度来看的话,在公共云上可以节省70%的资源,从复杂度或灾备演练的可操作性来看的话,这个优势也是非常大的,所以可以简单易行的做部署演练。

    云灾备给企业带来的核心竞争力:

    image.png

    灾备是企业最重要的一环,是信息安全里面的最重要的一环,它是带给企业核心竞争力的,如上图绿色部分所示。

    在云灾备的场景下,可以对数据进行很多的创新,不但可以降低IT的成本,可以复用这些数据做很多其它事情,同时也会带来业务上的创新,例如可以用云上灾备数据做些数据分析,数据发掘,可以做开发环境加速开发的效能,或是做一些只读的数据分析,可以带来更多的数据发掘。Cloud Backup也是很流行的一个方向,将备份的数据化静态数据为宝来降低IT成本,加速业务创新。

    云上的灾备可以增强数据保护和业务连续性能力,因为这里面提到,常态化的演练可以对数据保护和业务能力进行及时的修改,真正到故障来的时候可以做到很好的切换和替换,可以及时的保障业务的连续性。

    阿里云企业级云灾备解决方案(混合云备份服务):

    image.png

    总的来说云的灾备有这些核心竞争力,优势,解决传统方案的痛点。

    混合云的备份产品:可以从本地的机房分配到公共云上,或者将公共云备份到另一个机房中,拥有非常丰富的场景和环境。

    例如这些File/sql server等等这些应用文件块存储,可以非常便捷的进行线上至线下,或者线下至线上的一个备份。

    安全:全链路的加密,数据从本地中心,从备份的网关进行加密以后传到云上,云上也是加密存储进行落盘。

    可靠:在全链路进行数据校验,在阿里云上也及时进行周期性的校验,使得你的数据是不会丢失不会错的。

    便宜:1.有自己创新的重删和压缩的技术,重删和压缩比高达30:1。

    2.云存储本身的性价比高,这些备份放在对象存储,NAS,块存储本身它自己的性价比比较高。

    阿里云混合云备份服务2019年被评为数据保护创新奖
    阿里云企业级云灾备解决方案(混合云容灾服务):

    image.png

    全托管的一个容灾工作服务,它具备非常特别的业务能力,第一个是保障业务的连续性,它可以实现秒级的这个RPO,以及这种分钟级的这个RTO,然后也满足提供丰富场景和模式的需求,例如混合云备份中提到的一些产品,把线下的这个本地机房的物理虚拟机或者物理机可以备份容灾到这个云上来做一键演练,并且可以一键恢复。

    image.png

    总结来说云灾备解决方案里面有以上的优势:低成本、速度快、常态演练、多种模式、安全可靠。

    云上,更安全更可靠:

    image.png

    在阿里云上,数据是更加安全更加可靠的。因为提供了一系列的产品和技术的解决方案来让数据在云上更加安全更加可靠。

    第一个例子就是怎样让数据上来以后数据丝毫不能错。

    第二,通常说到的高可靠,有12个9的这种可靠性。高可用,不管出现什么问题,随时随地需要访问这个数据,读写这些数据的情况都可以访问。

    再就是保证不出错,数据存在阿里云上怎样保证不错?在上传数据的时候做端到端的数据完整性校验,在数据存储至阿里云以后,定期进行数据的扫描,保障数据不会因为磁盘的静默性的错误带来损失。

    第三是通过跨多个数据中心的可用区来达到高可靠高可用。

    当数据上传至阿里云,使用其跨可用区的纠删码进行校验,通过数据分片保障多个可用区的协调性与可靠性。

    可以通过分片容忍数据的损坏,只要通过6个数据进行恢复,这就是容忍AZ级别的故障损坏,通过别的分片进行恢复可用。

    数据保护,可靠,可控,可见:

    image.png

    在云上数据保护是可靠可控可见的,进行全链路的数字加密。不论在安全方面或是计算方面以及存储方面我们都进行了加密,所以这个数据是可靠的。

    这个加密呢,是可控的,就是客户,他自己可以来控制它的这个密钥,比如可以用我们的这个密钥管理系统,也可以用他自己的,这个不是一个MP,他自己带来的这个密钥,因为它也可以用,我们这种就是硬件内存的原理。

    就是这种硬件支撑的这种密码管理器,所以上这个密码的这个管理是可控的,然后第三个呢,是可见,就是我们对客户的数据的一些,比如说像内部的操作,一些日志也透明化给客户,然后客户自己对这些数据的读写的访问呢,客户他自己可以来打开,他的那个访问日志也可以来做可见的。

    像数据的运维和因公都可以可见的拿到内部的操作日志。

    最完整的存储产品和服务:

    image.png

    提供完整的产品和服务:右下角提供了基础的存储服务,左下角提供了混合云的存储矩阵。链接两者之间的数据迁移服务,在线迁移服务或是离线迁移(闪电立方)。

    在这些服务之上呢建立了数据管理服务,例如备份服务,容灾服务,日志服务(纯托管一站式管理分析服务),智能媒体管理(集成阿里巴巴达摩院的技术分析,抽取图片数据的结构化数据)。

    总结:阿里云的灾备服务它具备低成本、安全可靠、随时放心的特点。

    三、阿里云混合云灾备应用场景实践与案例解读

    (一)、阿里云混合云灾备服务简介

    混合云备份服务(HBR)

    image.png

    混合云备份服务为用户自有机房和阿里云上数据提供统一备份的BRaaS(备份即服务)。

    特点:
     经济:轻量级的能够直接以纯软件的方式为用户实现本地备份及云上备份的统一,它的经济成分是比较高的,最高可以节约80%TCO。

     高效:数据重删压缩比高达30:1,高效重删提升数据保存、传输效率降低带宽使用为整体效率带来非常大的好处。

     安全:数据上云实现端到端的严苛的AES-256加密技术。

     可靠:采用端到端的链路传输。加上后台的定期扫描确保一致性及数据的可靠性。

     监控:整套监控运维都在平台上,备份任务失败主动通知,直接以短信或者电话方式通知到管理员,出现问题可以第一时间查询到。

    提供的服务有本地上云备份,云上数据备份(支持跨地域),VMware云上备份容灾。

    HBR技术架构:

    image.png

    适用场景:
    1.本地上云备份---本地文件、数据库、虚拟机的云备份。

    2.云上数据备份---ECS主机数据、以及NAS/OSS自动备份
    HBR技术架构可以是纯软件或者是软硬结合的技术架构,它可以从源端通过重删,压缩,加密之后在上到云上去。像NAS/OSS可以通过云原生的方式,无需安装,无需部署其他的云资源的方式备份到云备份库,备份到云备份库不需要做任何管理,也不需要额外购买ECS等全自动备份策略,可以按需扩容。

    HBR高效重删技术-精准识别重复数据:

    image.png

    拿手的高效重删技术,通过变长重删,两个数据源精准识别数据重复部分:当你在两个类似内容文件,不同版本可以精确实现重删。

    混合云容灾服务(HBR):
    为阿里云企业级本地应用,云上应用提供高性能整机容灾服务,目标性能达到秒级RPO,分钟级RTO。优势:一体机部署、实时复制,无中断演练、一键切换。

    image.png

    适用场景:
    1.本地应用云容灾---官网、OA、核心业务容灾上云。

    2.云上跨地域容灾---阿里云上部署的CRM.OA等应用跨地域容灾。

    HDR-持续数据复制(CDR)技术:

    image.png

    1、HDR基于CDR,是个磁盘级IO复制,一旦落盘就会从内存上拷贝到云上。

    2、数据落到云盘上会基于数据的云盘及云盘快照保护可以短时间的把整机的服务拉起来向外提供服务了。

    3、在实现高性能的前提之下还确保资源的精致利用,云上的计算资源消耗仅仅占云下的15%。

    (二)、云灾备典型案例解读

    典型案例-影视制作公司海量数据日常备份

    image.png

    有150TB的编辑数据,每天都有变化,需要每天都要备份一下,数据放在大规模NAS集群上,在用户的本地机房虚拟机安装HDR备份软件,分任务对150TB数据扫描备份,通过高效的重删压缩技术,在2GB专线带宽的情况下将备份窗口控制在8小时内,将大量数据集中到同一个备份库,当大量数据集中到同一个备份库之后它整体的重删压缩比会更高。

    所以在这样一个场景中,用阿里云单个备份库就实现了150TB的数据备份。

    同时云上资源非常有弹性的,可能第二天有项目上的变化存在数据的增长和变化,那么在云上按需的弹性伸缩就是非常好的优势。

    只需配置好文件夹就会基于云上数据的动态进行调整,这样就会按使用量付费,而不是按照最大的量规划运算。

    典型案例-地产公司全站统一备份

    image.png

    在云下,它自己就自建了两个数据中心,同时他也一部分业务跑在云上的。

    数据中心混合云架构是现在企业的IT系统发展的一个非常典型的一个发展方向,那么其实已经有部分业务上云上,同时又有一部分业务是在云下,其实那个设计到混合云架构,用户希望看到的是什么?

    是一个统一的,完全统一的,有一个管理员在一个窗口就能够管理起来的一个灾备系统,那么这次如何来实现呢?就用阿里云的这个备份服务来实现了,我们呢,最后复制的特点就是我们可以软硬件部署这样的话呢,就是在用户本地有灾备一体机,在云上我们有软件版的灾备ECS。

    这样的话,就是整个服务在云上,云下完全打通,数据双向复制,也就是说本地的数据先在本地备份,在云上备份,这样的话呢,就是有一个本地备份加异地备份叫双备份的这么一个机制来给用户的数据上个双保险。

    同时呢,云上的数据也能够复制到远程的一体机上,那么,这样可以就实现了一个完全符合等保规定的把一个备份突破,同时整套备份。

    它支持Oracle,SAP文件,还有其他类型的应用,另外还有大数据的应用。

    它的管理入口是在云上的,那么在云上就可以方便的,就是在任何时间,任何地点都可以方便地管理这套备份系统。

    典型案例-石化公司核心系统云容灾

    image.png

    希望实现低成本有足够物理距离的容灾系统,系统大概有十几台服务器,希望实现分钟级的RPO,确保分钟级的RPO情况之下同时也能兼顾到整体的成本,那么混合云容灾服务给到的方案是给用户本地上海部署一个灾备一体机,负责用户生产系统的数据聚合压缩加密复制到阿里云上,利用阿里云的HDR的CDR持续数据复制技术,建立一个一对一的数据盘的复制,云上只需非常低的内存,只要准备好一对一的云盘,那么云下的服务器上的盘上数据都可以实时复制到阿里云上。同时这些赋予ECS(承载ECS)云盘规格都很低,平常都需要运行15%CPU内存的ECS就可以了,当用户只需要容灾演练或故障切换的时候才需要把这些真正的ECS拉起来,也就是说云上的按需拉起的ECS的规格是可以更大的是用户可以按需指定的,那么这些ECS在容灾演练的时候可能只需要运行1-2个小时来验证数据的正确性。

    这些ECS是按需使用费用是非常低的,每天只有容灾演练的几天左右。

    ]]>
    DTS数据迁移 | 《DTS控制台入门一本通》第三章(上篇)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    点击免费下载
    《DTS控制台入门一本通》>>>

    test

    也可以PC端点击https://developer.aliyun.com/topic/download?id=803 下载

    数据迁移可以把源端数据库的数据迁移到目标端(迁移不会影响源端数据库的数据,也不会导致源端数据库的数据丢失),数据迁移只支持后付费(按量付费),该页面展示了当前账户下各个地域的数据迁移实例的运行状态,如图 3-1。您还可以在这个页面创建新的迁移任务以及查看当前迁移任务的详细信息。

    image.png

    3.1地域

    图 2-1 标记①处为地域信息,此处可以切换各个地域查看不同地域的实例信息。
    推荐从概览页进入。

    3.2文件导入

    图 3-1 标记②的“文件导入”功能,是使用 DTS 提供的一个客户端工具,将工具部署在本地,使用该工具将本地数据库导出的文件,然后上传,实现导入功能,相当于我们自己导出文件然后导入一样,如图 3-2,该功能已经不再支持使用,请勿使用。

    image.png

    3.3创建迁移任务页面

    图 3-1 标记②的“创建迁移任务”,可以进行数据迁移的任务配置,点击后,会 进入 DTS 迁移任务的配置界面,如图 3-3,需要注意:

    ● DTS 目前的实现是逻辑迁移。
    ● 逻辑迁移的意思是指 DTS 会借助 SELECT 的方式抽取源库已存在的数据, 然后再通过 INSERT 的方式写入到目标库。
    ● 目前还不支持物理文件层面的迁移。

    image.png

    DTS 迁移任务主要分为 2 部分进行配置,即源库信息和目标库信息,源库是指 要使用 DTS 迁移的源端的数据库,目标库是指待迁入数据的数据库。比如我们要把 MySQL A 数据库的数据迁移到 MySQL B 数据库,这里,MySQL A 数据库 A 就是 源库。目标库就是 MySQL B 数据库。下面对源库以及目标库配置过程的具体项目进 行讨论。

    3.3.1 实例类型

    如图 3-3,顾名思义,就是 DTS 支持迁移的源端数据库接入的类型,并且 DTS 对不同的接入类型的实例,有不同的支持粒度,主要分为如下几种。无论是哪一种, 最终实现的核心目的是能让 DTS 的服务器集群连接到源端或者目标端的数据库。

    3.3.1.1 有公网 IP 自建的数据库

    这是最简单的一种接入类型,就是指您的源端的数据库有公网 IP 地址,这个地 址允许其他程序 ( 这里特指 DTS) 可以通过您的公网 IP 连接到您的源端数据库 , 这种 实例类型配置比较灵活,但是容易受到公网网络环境的影响,进而影响迁移的速率。 当我们选择这个类型时,页面的信息也会按照“有公网 IP 自建的数据库”进行展示, 如图 3-4。您需要把源端数据库的公网连接地址填写到“主机名或者 IP 地址”里, 这个需要再次强调,这个 IP 地址必须能够让 DTS 服务器访问到,即连通性必须正 常。如果连通性有问题,在“测试连接”时会出错。我们会在“测试连接”部分详细 讨论。这里有 4 点需要说明:

    ● 虽然这里特指的是“自建数据库”,但是只要是公网可达的 ( 比如 RDS 的公网 地址 ),都可以使用这个方式进行传输。
    ● 这里指的 IP 并不是只可以填写 IP, 也可以填写域名 ( 比如 RDS 的公网连接字 符串 )。
    ● 自建 MySQL 数据库一般都有一个参数 bind_address,这个是指 MySQL 接 受(监听)来自于哪个 IPV4 或者 IPV6 地址的连接,为了避免出现 DTS 无法 连接的情况,建议设置成“*”或者“0.0.0.0”。
    ● 请一定确保,自建数据库所在主机的防火墙没有拦截 DTS 服务器地址的入方
    向的访问。服务器地址我们会在“3.3.2 实例地区”讨论。

    image.png

    3.3.1.2 通过专线 /VPN 网关 / 智能接入网关接入的自建数据库

    相比公网的不安全而言。这个方式实现了非“公网”传输。但是这里的非“公 网”传输需要借助其他的产品来实现,这些产品是:高速通道专线智能接入网关 VPN 网关。它们最终实现的是把本地自建数据库和阿里云的链路打通。通过非“公 网”的方式进行连接。最终只要连通性正常。就可以进行传输。如下图 3-5。这里有 5 点需要特别说明:

    ● 虽然它们都可以实现非“公网”方式连接。但是实现方式是不一样的。专线才 是真正意义上的内网传输。而智能接入网关和 VPN 网关都是依赖公网实现的。 只是实现了非“公网”方式连接 ( 打通了本地与阿里云内网 )。
    ● 选择这个方式,需要填写 VPC ID(“已和源端数据库联通的 VPC”)。请注意, 这里不是随便填写一个 VPC ID 就可以了。这个 VPC ID 必须进行了高速通道 专线、智能接入网关、VPN 网关的相关配置打通了链路才可以。没有做任何 配置的 VPC ID 即使填写上也无法联通 ( 测试连接会出错 )。具体配置方式涉 及各个产品的较多内容,您如果需要可参考具体产品的帮助文档。
    ● 虽然这里特指的是“自建数据库”,但是只要是通过这三种方式实现网络可达 的 ( 比如 VPC 下的 RDS),都可以使用这个方式进行传输。 ●“IP 地址”这部分,只可以填写 IP 地址。如何填写域名或者字符会提示“请输 入合法的 ip 地址”。 ● 配置好高速通道专线、智能接入网关、VPN 网关之后,还需要配置 DTS 与它们 之间路由,配置路由需要参考:
    https://help.aliyun.com/document_detail/117525.html?spm=a2c4g.11186623.6.590.2d2f6487R0gxRt

    image.png

    3.3.1.3 无公网 IP:Port 的数据库 ( 通过数据库网关 DG 接入 )

    如下图 3-6,选择这个接入方式,也可以实现非“公网”接入。它的实现方式 是要在源端数据库所在的主机上安装一个数据库网关。这个数据库网关与智能接入网 关、VPN 网关相似,依赖于公网。但是免去了复杂的配置步骤与较高的成本。这种 接入方式的使用量较少。使用时需要注意如下 1 点:

    ● 这里配置时需要选择“数据库网关 ID”,这里不是选择了 ID 就可以了,还需 要对这个网关进行安装和添加数据库。也就是要保证网关“状态”运行正常。

    image.png

    3.3.1.4 通过云企业网 CEN 接入的自建数据库

    如下图 3-7,云企业网简称 CEN(Cloud Enterprise Network),它不是一种具体 的接入方式,即它并不是专线、VPN 这种连接能力。它是一个能力提供者或者平台。 它提供一种组网的能力,可以实现专线、VPN 等的网络互通与管理。选择这个接入方 式后,需要选择“云企业网实例 ID”以及“已和源库互联的 VPC 网络”。这里特指 “自建数据库”。但是无论是自建还是云数据库,只要连接打通,都可以使用这种方式。

    image.png

    3.3.1.5 ECS 上的自建数据库

    顾名思义,就是指在阿里云 ECS 服务器上搭建的自建数据库,如下图 3-8,选 择这个接入方式非常适合 ECS 服务器上的数据库进行迁移。您只需要选择对应的 “ECS 实例 ID”,填写相关数据库信息即可。此处需要注意 2 点:
    ● ECS 服务器有安全组的限制。正常情况下,当选择完 ECS 的实例 ID,填写 完数据库的连接信息,然后点击 DTS 源端数据库的“测试连接”后,DTS 会 自动把 DTS 服务器的 IP 端添加到 ECS 的安全组里面。如图 3-9。如果您遇 到连通性问题,请先检查这一点是否正常。
    ● 除了安全组,ECS 上部署的操作系统内还有防火墙,请确保防火墙也做了响 应的入方向放行规则。

    image.png

    image.png

    3.3.1.6 RDS 实例

    这里特指您购买的阿里云的 RDS 数据库实例,如下图 3-10,选择这个接入方式后,只需要填写对应的“RDS 实例 ID”以及账密即可。DTS 后台会自动的通过实例 ID 查询对应 RDS 的连接地址和端口。并且,DTS 还支持“其他阿里云账号下的 RDS 实例”的迁移。您可以在阿里云 B 账号下迁移阿里云 A 账号的 RDS 数
    据库。跨账号迁移的操作与配置比较复杂,可以参考该云栖文档:
    https://yq.aliyun.com/articles/353204?spm=a2c4e.11155435.0.0.6ff363b5zVAhec

    此处注意如下 1 点:

    ● RDS 数据库产品有一个安全限制,就是白名单。同“3.3.1.5 ECS 上的自建数据库”的自动添加安全组行为类似,DTS 也会自动把 DTS 服务器的地址段添加到 RDS 数据库的白名单中。添加完后的白名单信息您无法在云 RDS白名单页面看到。

    image.png

    3.3.1.7 云 MONGODB 实例

    这里特指您购买的阿里云的 MONGODB 数据库实例,如下图 3-11,选择这个接入方式后,只需要填写对应的云 MONGODB 的实例 ID、认正数据库以及账密即可。同“3.3.1.6 RDS 实例”的自动添加白名单行为一样,DTS 也会自动把 DTS服务器的地址段添加到云 MONGODB 数据库的白名单中。添加完后的白名单信息您无法在云 MONGODB 的白名单页面看到。

    image.png

    3.3.1.8 PolarDB

    这里特指您购买的阿里云的 PolarDB 数据库实例,如下图 3-12,选择这个接入方式后,只需要填写对应的云 PolarDB 的实例 ID 以及相关的连接信息即可。同“3.3.1.6 RDS 实例”的自动添加白名单行为一样,DTS 也会自动把 DTS 服务器的地址段添加到云 PolarDB 数据库的白名单中。添加完后的白名单信息您无法在云PolarDB 的白名单页面看到。

    image.png

    3.3.2实例地区

    图 3-3 中的“实例地区”选择后也就意味着 DTS 会使用这个地区的服务器(DTS 在很多地区都部署了服务器)连接源端数据库进行数据的抽取和传输(简单说 就连接数据库后执行 select 查询获取数据)。选择不同的实例类型,在实例地区选择 上有 2 点不同,主要如下:

    ● 如果实例类型选择的是“有公网 IP 自建的数据库”,实例地区这里理论上选 择任何一个都可以(因为公网 IP 任何地方都可达),建议选择与数据库所在 地域物理距离相近的地区。比如自建数据库的机房在北京,则建议选择华北 2 地区。

    ● 除“有公网 IP 自建的数据库”外的其他实例类型在选择实例地区时,则需 要按照实例所在的地域进行选择。比如 RDS 实例在华东 1,则地域必须 选择华东 1。这样的话,DTS 的华东 1 服务器集群才可以正常连接华东 1 的 RDS。

    重点再说明一下实例类型部分说明的问题,因为 DTS 的服务器非常多,为了能 够让 DTS 顺利的链接上源端的数据库,假设您源端数据库做了防火墙 ( 自建 )、安全 组 (ECS)、白名单 (RDS) 等安全设置,您还需要把 DTS 这个地区的所有服务器(有 的客户不想放行所有服务器,这个暂时无法满足)的地址进行放行。需要放行的 DTS 服务器的网段点击图 3-3 的“获取 DTS IP 段”查看,如下图 3-13。

    image.png

    3.3.3端口

    图 3-3 的“端口”部分用来指定需要 DTS 访问的数据库端口,只有某些实例类型才需要填写该项目。指定了 IP 只能说明您的数据库运行在这台主机上,但是这台主机上可能运行了很多的应用程序。端口就用来说明要访问哪个应用程序。请注意您的网络防火墙或者安全组 ( 特指 ECS) 配置。对对应端口的访问进行放行。

    3.3.4数据库类型

    图 3-3 的“数据库类型”部分用来选择迁移的数据库类型,目前阿里云 DTS 支 持的源端数据库类型有:Oracle、MySQL、SQLServer、PostgreSQL、Mongodb、 DB2、TIDB。这里需要正确的选择源端数据库的类型,如果您源端是 MySQL 则需 要选择 MySQL。您选择什么样的数据库类型意味着 DTS 的应用程序使用哪种数据 库驱动进行连接您的源端数据库,所以请务必选择正确。这里有 3 个注意点:

    ● 源库的数据库类型与目标库的数据库类型建议一致,因为兼容性最好。以下 图 3-14 为例,当选择实例类型为 RDS,实例 ID 是一台 MySQL 实例时, 目标端数据库类型或者实例类型时会展示出 SQLServer、Postgresql。目 前 DTS 除 了 支 持 MySQL->Oracle、Mysql->Postgresql、MySQL-> MySQL、MySQL-> PolarDB MySQL、MySQL-> AnalyticDB MySQL、 MySQL-> DRDS 外,其他的数据库类型暂不支持,所以如果此时目标选择 SQLserver 实例,迁移会出现异常。其他的实例类型和数据库类型同理。

    ● 若源端数据库为 Sqlsever 时,当开启增量迁移时,一个 DTS 任务只允许一 个数据库进行增量迁移(这取决于其事务日志)。全量迁移支持多个数据库。

    ● 若源端数据库为 PostgreSQL 时,无论是否开启增量迁移,一个 DTS 任务只 允许迁移一个数据库(这取决于其 XLOG)。

    image.png

    3.3.5数据库账号

    图 3-3 的“数据库账号”指的是您需要 DTS 使用哪个账号连接您的源端的数据 库。这里出现问题最多的是 MySQL 数据库的账号问题以及 MONGODB 数据库的 账号问题。主要有如下 4 点:

    ● MySQL 的数据库账号组成是 user 和 host 两个元素,即:user@host 这种 格式。这也就意味着,如果 user 相同而 host 不同,这不是相同的账号。比如 如下 2 个账户:alitest@'%' 与 alitest@'10.0.0.1'。 当 您 使 用“alitest” 账 户 在 IP 为 10.0.0.1 的客户端主机访问您的数据库的时候,鉴权时账户用的是 alitest@'10.0.0.1'。当您使用“alitest”账户在 IP 为 10.0.0.2 的客户端主机访 问您的数据库的时候,由于“alitest”的 host 只有 % 和 10.0.0.1。而 10.0.0.1 并不符合,所以鉴权时账户用的是 alitest@'%'。了解这点非常重要。

    ● 所以,我非常建议您创建一个独立的账号进行 DTS 的迁移,并且账号的 host 建议是 '%'。因为 DTS 服务器的网段非常多,并且没有规律(请参考图 3-5)。% 可以有效的避免账户连接多问题。

    ● MySQL 遇到最多的数据库账号连接类问题是这个错误 : Access denied for user 'xxxx'@'xxxx' (using password: YES) 这个错误在连接时产生的原因有 2 个:账户错误或者密码错误。您如果遇到, 请参考这里排查: https://yq.aliyun.com/articles/158321?spm=a2c4e.11155435.0.0.6ff363b5cXNuax

    ● MONGODB 的账户链接时,还需要填写一个“数据库名称”项,如下图 3-15。这个是指 MONGODB 账户的 authentication database。 authentication database 是指创建 MONGODB 账户的时候所在的数据库。比如一 个 MONGODB 数据库为 alitest。我们执行 use alitest 切换到 alitest 数据库 下,然后执行 db.createUser() 创建一个 test 账户,密码是 123。当我们使 用 test 账户登陆 mongodb 的时候,数据库名称 (authentication database) 就是 alitest。

    image.png

    3.3.6数据库密码

    图 3-3 的“数据库密码”指的是使用的“数据库账号”的连接密码。数据库密码遇到的问题较少。请重点关注“数据库账号”章节的相关问题。

    3.3.7连接方式

    当实例类型选择“ECS 上的自建数据库”,数据库类型选择“MySQL”时, DTS 支持连接方式的选择,即支持 SSL 加密的连接方式。当勾选“SSL 安全连接” 时,需要上传 SSL 的证数等文件,如下图 3-16。其中 CA 根证数是必传的。另外, DTS 目前只支持 SSL 加密。请注意如下 2 点:

    ● 如您要使用 SSL 安全连接,请确保您的源端数据库以及目标端数据库做了相 应的 SSL 配置,关于如何配置数据库的 SSL 非本书的重点。不在此赘述。

    ● 并非所有的实例类型以及数据库类型 DTS 都支持 SSL 访问。

    image.png

    3.3.8测试连接

    重点中的重点,图 3-3 的“测试连接”点击后,会使用我们配置的这些连接信 息去连接源端或者目标端的数据库,针对 ECS、RDS、云 MONGODB、云 REDIS 等云数据库,还会进行安全组、白名单的检查和添加操作,检查对应的云 ECS 实例 和云数据库实例的安全组、白名单里是否有 DTS 的安全组或者白名单,没有则添加。 测试分为 ping、telnet、数据库协议 ( 比如 MySql JDBC Connect ) 三个层面,测 试结果如下图 3-17。需要注意如下 4 点:

    ● 如果测试连接失败。分为 ping 失败,telnet 失败以及数据库协议 ( 比如 MySql JDBC Connect) 失败。我们只需要关注 telnet 和数据库协议即可。因为 ping 现在的测试结果并不能说明问题,可以忽略它的测试结果。

    ● telnet 如果成功,说明 DTS 可以正常通过您配置的地址与端口访问到对应的应 用程序。如果失败,则说明 DTS 无法通过您配置的地址与端口访问到您的应用 程序。此时则需要进行网络防火墙或者安全组的排查或者抓取网络报文排查。

    ● 数据库协议 ( 比如 MySql JDBC Connect ) 如果成功,则说明 DTS 可以通 过您配置的地址、端口、数据库账号、数据库密码等信息,成功访问到您的数 据库。如果失败,一般会有错误提示,对 MySQL 来说,最常见的是 Access denied for user 'xxxx'@'xxxx' (using password: YES), 这 点, 我 们 在 “3.3.5 数据库账号”讨论过。其他的连接问题,可以参考这个云栖文档 : https://yq.aliyun.com/articles/158321?spm=a2c4e.11155435.0.0.6ff363b5aL5jmA

    ● 有时点击测试连接后,会一直转圈,没有任何结果。遇到这个情况。可以忽略 测试连接,直接点击“授权白名单并进入下一步”。换句话说,测试连接并不 是必须要点击的。如果配置的源库连接有问题,我们点击“授权白名单并进入 下一步”跳转到新页面时会出错 ( 因为此时会真正的去源库取数据库的对象信 息,如下图 3-18)。如果配置的目标库连接有问题,我们在“预检查”时会 出错 ( 因为预检查会检查目标库连接 )。所以测试连接一般情况下可点可不点。 大多数情况下不需要执着于这一步。

    image.png
    image.png

    3.3.9授权白名单并进入下一步

    图 3-3 的“授权白名单并进入下一步”点击后,会和测试连接一样,也会进
    行安全组与白名单的检查和添加,然后会进入数据库迁移对象选择页面,如下图3-19。这是一个库级别 ( 数据库级别的同步,DTS 还支持表级别和列级别同步 ) 同步的任务截图。这个页面的左侧显示的是源端数据库的对象信息 ( 如图例里的 dtstest、mysqltest 数据库 )。右侧显示的是要迁移到目标数据库的对象信息 ( 如 图例里的 dtstestdata 数据库 ),如果您要更改迁移到目标数据库的对象的名字, 可以把鼠标放到“dtstestdata”上,然后点击右侧的编辑,更改库名 ( 除了更改库 名外,表级别同步还支持修改表名以及列名 )。该功能为 DTS 的库表列映射,如下 图 3-20。

    image.png

    image.png

    image.png

    3.3.10迁移类型

    3.3.10.1结构迁移

    图 3-19 里的“结构迁移”是指是否迁移源端数据库的对象结构信息。这些结构 包括表结构、索引、视图、存储过程等。DTS 会通过 Select 的方式,获取源库的数 据对象的结构。为什么要进行结构迁移?什么时候需要结构迁移?原因分别如下:

    ● 结构迁移针对的是关系型数据库,非关系型数据库不需要 ( 比如 MONGODB),对关系型数据库来说,数据要存储,必须先建表。所以 DTS 需要先 进行结构迁移,创建表结构才能进行后面的数据迁移。

    ● 当目标数据库里面没有对应的表对象时,才需要选择结构迁移。如果目标数据 库里面已经有了对应的数据库的表对象,不需要选择结构迁移 ( 比如目标库已 经有表 A,此时选择结构迁移,DTS 会在目标库创建表 A,因为表 A 已经存 在,DTS 创建会出错,提示 1050 - Table xxxx already exists)。

    结构迁移遇到的最常见的问题是 :

    ● 源端或者目标端的数据库的数据库对象非常非常多,因为 DTS 要以 Select 的 方式查询这些信息。数据库对象过多,容易造成查询超时,这类问题比较难解决。

    如果您遇到这种问题,请反馈阿里云售后。 如果要查看 DTS 在源端或者目标端的进程状态,大多数数据库都可以在源 端或者目标端的执行数据库的相关查询命令,以 MySQL 为例,可以执行 :show processlist 确认会话信息。

    3.3.10.2全量数据迁移

    图 3-19 里的“全量数据迁移”是指是否迁移源端数据库的数据 ( 表的记录 )。 这是真正的数据的迁移,它迁移的是源端数据库表里已经存在的数据(这些数据可 能是历史数据,早已经写入,也可能刚刚写入不久的数据,非未来新增的数据),简 单的说,DTS 会通过 Select 获取源端数据库里的表的数据。然后通过 Insert、 Replace into、Update、Delete 的方式,写入到目标端。DTS 使用 Select 查询对 应表的数据,不是一次性全部查询的。一般是对的数据进行分片 ( 常见的是使用主键进行分割 ),然后并行查询各个分片的数据。DTS 写入目标端数据也是并行写入的。 这也就导致有 2 个问题:

    ● DTS 迁移完成后,目标数据库的数据空间大小比源端数据库的数据空间大小 大,这是因为并行写入产生数据空洞导致。如果您遇到这种问题,可以在业务 允许的期间执行 Optimize table xxx 对表的空间进行收缩 (Optimize table 有 产生 MDL 阻塞的可能 , 执行时请评估执行时间和业务影响 )。

    ● Optimize table 大多数情况下,都能够成功的对表的空间进行收缩。极少的情况 下无法对表的空间进行收缩。如果遇到无法收缩的情况,可以反馈阿里云售后。 如果要查看 DTS 在源端或者目标端的进程状态,大多数数据库都可以在源 端或者目标端的执行数据库的相关查询命令,以 MySQL 为例,可以执行 :show processlist 确认会话信息。

    3.3.10.3增量数据迁移

    图 3-19 里的“增量数据迁移”是指 DTS 通过解析源端数据库的相关数据库日 志 ( 比 如 MySQL 的 Binlog、SQLserver 的 Transaction Log、MONGODB 的 oplog 等 ),把全量迁移开始之后的增量数据,实时的同步到目标端。即源端有一条 Insert,DTS 会解析这些日志,生成一个同样的 Insert 发送到目标端,实现增量数 据迁移,增量迁移目前不支持触发器的迁移。

    很多客户在问,DTS 是如何保证数据完整性的? DTS 通过全量 + 增量的方式 保证数据完整性,但是在如下 2 个情况下,DTS 无法保证数据的完整性:

    ● 迁移的对象里,没有主键或者唯一键的数据库对象,无法保证完整性。因为 DTS 是分批次抽取和并发写入数据,没有主键时会造成数据重复或者数据缺 失的情况。请确保迁移对象有主键或者唯一键。

    ● 多对一的相同数据库对象的数据迁移,无法保证数据完整性。多对一是指多个 源库通过多个任务,把源端多个数据库对象的数据迁移到目标库的同一个数据 库对象里。 如果要查看 DTS 在源端或者目标端的进程状态,大多数数据库都可以在源端或者目标端的执行数据库的相关查询命令,以 MySQL 为例,可以执行 :show processlist 确认会话信息。DTS 的增量会在源端的 MySQL 数据库启动一个 binlog dump 进程(如果您搭建过 MySQL 主从,MySQL 本身的主从也是会起动一个 binlog dump 进程)。 DTS 的增量数据迁移延迟是无法保证的,正常情况下 DTS 的增量迁移是秒级延 迟,但是当遇到一些 DDL、大量更新时或者 DTS 规格达到瓶颈等情况时,增量数据 迁移延迟会增高。如果您遇到大的延迟(比如超过 1000S),可与阿里云售后反馈。

    3.3.11预检查并启动

    当迁移对象以及迁移类型配置好后,点击“预检查并启动”将会进行下面的检 查环节,检查环节顺利通过后,会启动 DTS 任务开始迁移数据。检查的目的是避免 一些已知的问题导致 DTS 迁移异常,常见的检查项目如下图 3-21。预检查失败后, 无法进行下一步,需要您针对出错的检查项进行处理,当您处理了预检查失败的检查 项后。可以点击启动任务(启动方式为图 3-1 中任务列表的“启动任务”按钮。)重 新启动预检查。

    image.png

    3.3.11.1源库连接性检查

    检查预检查的 DTS 服务器是否可以通过迁移任务页面的相关配置正常连接源端 数据库。需注意如下 2 点:

    ● DTS 的服务器有很多,这些服务器有的部署了预检查程序,有的部署了“测 试连接”程序,有的部署了结构迁移的程序,有的部署了全量迁移的程序,有 的部署了增量迁移的程序。这也就意味着,任何一个环节的连接正常都不能保 证下一个或者其他环节连接也正常 ( 比如数据库等防火墙做了限制 )。您可能 会遇到预检查正常但是后续的结构迁移连接异常,也可能遇到结构迁移任务正 常但是后续的全量迁移连接异常的情况。这些都是有可能的。

    ● 遇到连接异常的情况,首先要有一个判断,这个异常是网络本身 (TCP/IP) 不通还是应用层面的问题。如果是网络层面的,则需要查防火墙以及链路 配置。如果网络没问题只是在应用层出现的异常,则需要查造成这个应用异 常的原因。举一个例子,前面提到过的这个错误 : Access denied for user 'xxxx'@'xxxx' (using password: YES)。这个错误意味着,网络可达,只是 在对数据库进行账密认证的时候出错了 ( 应用层 )。如果网络不可达,我们甚 至无法进行数据库应用层面的这些认证。了解这点非常重要。

    3.3.11.2源库权限检查

    要使 DTS 通过您在创建迁移任务页面填写的账户信息获取源端数据库的数据, 就需要对这个账户进行一写数据库对象的授权。不同的数据库类型以及迁移类型需要 不同的数据库权限。以 MySQL 为例,结构、全量迁移只需要 SELECT 权限即可。 而增量迁移则需要 REPLICATION SLAVE、REPLICATION CLIENT、SHOW VIEW 和 SELECT 权限。

    3.3.11.3目的库连接性检查

    检查预检查的 DTS 服务器是否可以通过迁移任务页面的相关配置正常连接目标端数据库。注意点与“3.3.11.1 源库连接性检查”相同。

    3.3.11.4 目的库权限检查

    要使 DTS 把从源端获取到的数据写入目标端数据库,也需要对应的写入权限。 同样以 MySQL 为例,结构、全量和增量需要 SELECT 和 INSERT、UPDATE、 DELETE、CREATE 等等读写的权限。

    3.3.11.5 存储引擎检查

    这个一般是针对 MySQL 数据库的迁移,因为 MySQL 有很多数据库引擎。 DTS 不支持 FEDERATED、MRG_MyISAM 或 TokuDB 存储引擎的迁移。

    3.3.11.6 源库版本检查

    检查源库的版本是否符合 DTS 产品的要求,以 MySQL 为例,DTS 只支持如 下几个版本:5.1、5.5、5.6、5.7、8.0。其他数据库类型的版本限制可以参考这里: https://help.aliyun.com/document_detail/26618.html?spm=a2c4g.11186623.2.7.3a392a8770k06x#concept-26618-zh

    3.3.11.7 同名对象存在性检查

    目的是检查目标数据库中是否存在和待迁移对象同名的对象。当我们选择结构迁 移的时候,DTS 会在目标创建对象结构,如果目标数据库已经有同名的对象结构了, 就会创建失败。

    这点我们在“3.3.10.1 结构迁移”讨论过。

    3.3.11.8 数据库可用性检查

    检查目标实例是否已经有了对应的数据库,比如我们要把源端 alitest 的数据库 迁移到目标端。我们需要先在目标端手动创建 alitest 实例。避免预检查失败,请注 意如下 1 点:

    ● 很多情况下,DTS 会自动的在目标端创建数据库,DTS 自动创建数据库的 前提是账户的权限正常。不过也有例外(比如字符集问题,数据库名称不合法 等),如果遇到 DTS 无法自动创建数据库的情况,请手动创建后重启预检查。

    3.3.11.9 源库 binlog 开启检查

    当迁移类型勾选增量迁移时,才会检查该项,在“3.3.10.3 增量数据迁移”我们 讨论过,增量迁移是通过一些日志来实现的。这个检查项是为了检查数据库这方面的 日志是否开启。如果源端是 MySQL 数据库,则检查 binlog。

    3.3.11.10 源库 binlog 模式检查

    开启了相关日志后,要检查源数据库的 Binlog 模式是否为 ROW。建议更 改 .cnf 配置文件,改完配置文件的参数后,请对源端数据库进行一次重启。

    3.3.11.11 源库 binlog_row_image 是否为 FULL

    如果您源端是 MySQL5.6 及以上的 数据库版本时,开启增量迁移后,会检查 binlog_row_image 参数。建议更改 .cnf 配置文件,改完配置文件的参数后,请对 源端数据库进行一次重启。

    3.3.11.12 源库 server_id 检查

    如果您源端是 MySQL 数据库,则开启增量迁移后,会检查源端数据库的 server id 参数。server_id 值需要是大于 2 的整数。请注意如下 1 点:

    ● 建议更改 .cnf 配置文件,更改完配置文件参数后,请对源端数据库进行一次重启

    3.3.11.13 源库 binlog 存在性检查

    这个检查项目主要检查 show binary logs 列出来的的文件是否存储,检查这个 的目的是因为很多情况下,我们删除 MySQL 的 binlog 时,是直接从操作系统目录 remove 掉的,并没有通过 MySQL 的 purge 进行删除。这也就导致数据库里还存 在着 binlog 文件的记录,而操作系统目录里已经没有了。遇到这种情况出现,您可 以通过执行 purge 来清理 MySQL 数据库记录的 binlog 文件。使之与操作系统目录 的文件数量和编号对应。

    3.3.11.14 MySQL 密码格式检查

    检查源库使用数据库密码格式是否为老版。主要检查数据库的参数 old_ passwords 是否为 1。如果您的 old_passwords 为 1,建议修改为 0。修改密码格 式可能会影响业务的正常运行,请谨慎操作。

    3.3.11.15 复杂拓扑存在性检查

    DTS 支持的迁移拓扑一般有如下 5 种:一对一、一对多、级联、多对一、双向 迁移(非双向同步),其中双向迁移是不支持的拓扑结构,如果后台检测到这种拓扑结 构,会出错。如果这个检查项您出现预检查失败,一般可以联系和授权阿里云售后跳 过概检查项(预检查检测环节可以手动跳过的前提是双向迁移的数据对象以及数据不 冲突,避免造成数据混乱)。

    3.3.11.16 补充

    如果预检查出现这种错误检查项“unexpected error”,请与阿里云售后反馈确认。

    3.3.12 立即购买并启动

    预检查通过后,点击下一步,进入购买页面,如下图 3-22。选择规格后,点击 “立即购买并启动”正式运行 DTS 任务。这里需要注意如下 3 点:

    ● 对 DTS 迁移来说,只进行结构迁移和全量迁移,目前是免费的

    ● 选择增量迁移后,才会收费,暂停同样收费。

    ● DTS 迁移只支持后付费(按量计费),不支持包年包月。

    image.png

    ]]>
    判断“存在“你还在用count吗,细节决定成败-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 @[toc]
    在这里插入图片描述

    1. 为什么要用count(*)

    在我们实际开发中,业务 往往是 如:(判断该手机是否已经存在),我们为了提高程序的运行效率,这个时候都会使用

    select count(*) from sys_user where phone = '18217692100'

    在这里插入图片描述

    //而我们Java代码
    Integer count = mapper.selectCount(phone);
    //写业务代码
    if(count != 0){
      ……
    else{
      ……
    }

    2. 优化方案

    select 1 from sys_user where phone = '1821762100' LIMIT 1

    在这里插入图片描述

    //而我们Java代码
    Integer count = mapper.selectCount(phone);
    //写业务代码
    if(count != null){
      ……
    else{
      ……
    }
    • SQL不再使用count,而是改用LIMIT 1,让数据库查询时遇到一条就返回,不要再继续查找还有多少条了
      业务代码中直接判断是否非空即可
    • 正所谓 ,细节决定成败 ,从速度上可以看出我们的优化方案快了好多

    个人博客地址:http://blog.yxl520.cn/

    ]]>
    超详细的RabbitMQ入门,看这篇就够了!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 思维导图

    一、什么是消息队列

    消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。

    “消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,谁从消息队列中取出数据处理,他不管。消费者只负责从消息队列中取出数据处理,他不管这是谁发送的数据。

    二、为什么使用消息队列

    主要有三个作用:

    • 解耦。如图所示。假设有系统B、C、D都需要系统A的数据,于是系统A调用三个方法发送数据到B、C、D。这时,系统D不需要了,那就需要在系统A把相关的代码删掉。假设这时有个新的系统E需要数据,这时系统A又要增加调用系统E的代码。为了降低这种强耦合,就可以使用MQ,系统A只需要把数据发送到MQ,其他系统如果需要数据,则从MQ中获取即可

    • 异步。如图所示。一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。

    • 削峰。如图所示。这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃

    三、RabbitMQ的特点

    RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点,官网可查:

    • 可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
    • 灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
    • 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
    • 多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
    • 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
    • 可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
    • 插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。

    四、RabbitMQ初の体验

    4.1 安装RabbitMQ (Win10系统)

    由于只是学习需要,所以安装在win10系统,就懒得开虚拟机。如果用Linux系统安装的话,我建议用Docker拉一个RabbitMQ的镜像下来,这样会方便一点。

    4.1.1 安装erLang语言,配置环境变量

    首先到erlang官网下载win10版安装包。

    下载完之后,就得到这个东西:

    接着双击安装,一直点next(下一步)就行了,安装完之后,配置环境变量。

    使用cmd命令,输入 erl -version 验证:

    4.1.2 安装RabbitMQ服务端

    在RabbitMQ的gitHub项目中,下载window版本的服务端安装包。

    下载后,就得到这个东西:

    接着到双击安装,一直点下一步安装即可,安装完成后,找到安装目录:

    在此目录下打开cmd命令,输入rabbitmq-plugins enable rabbitmq_management命令安装管理页面的插件:

    然后双击rabbitmq-server.bat启动脚本,然后打开服务管理可以看到RabbitMQ正在运行:

    这时,打开浏览器输入http://localhost:15672,账号密码默认是:guest/guest

    到这一步,安装就大功告成了!

    4.2 永远的Hello Word

    服务端搭建好了之后肯定要用客户端去操作,接下来就用Java做一个简单的HelloWord演示。

    因为我用的是SpringBoot,所以在生产者这边加入对应的starter依赖即可:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

    一般需要创建一个公共项目common,共享一些配置,比如队列主题,交换机名称,路由匹配键名称等等。

    首先在application.yml文件加上RabbitMQ的配置信息:

    spring:
        rabbitmq:
            host: 127.0.0.1
            port: 5672
            username: guest
            password: guest

    然后再生产者这边,加上common包的maven依赖,然后创建一个Direct交换机以及队列的配置类:

    @Configuration
    public class DirectRabbitConfig {
        @Bean
        public Queue rabbitmqDemoDirectQueue() {
            /**
             * 1、name:    队列名称
             * 2、durable: 是否持久化
             * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
             * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
             * */
            return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false);
        }
        
        @Bean
        public DirectExchange rabbitmqDemoDirectExchange() {
            //Direct交换机
            return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
        }
    
        @Bean
        public Binding bindDirect() {
            //链式写法,绑定交换机和队列,并设置匹配键
            return BindingBuilder
                    //绑定队列
                    .bind(rabbitmqDemoDirectQueue())
                    //到交换机
                    .to(rabbitmqDemoDirectExchange())
                    //并设置匹配键
                    .with(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING);
        }
    }

    然后再创建一个发送消息的Service类:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        //日期格式化
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Resource
        private RabbitTemplate rabbitTemplate;
    
        @Override
        public String sendMsg(String msg) throws Exception {
            try {
                String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
                String sendTime = sdf.format(new Date());
                Map<String, Object> map = new HashMap<>();
                map.put("msgId", msgId);
                map.put("sendTime", sendTime);
                map.put("msg", msg);
                rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, map);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
    }

    然后根据业务放在需要用的地方,比如定时任务,或者接口。我这里就简单一点使用Controller层进行发送:

    @RestController
    @RequestMapping("/mall/rabbitmq")
    public class RabbitMQController {
        @Resource
        private RabbitMQService rabbitMQService;
        /**
         * 发送消息
         * @author java技术爱好者
         */
        @PostMapping("/sendMsg")
        public String sendMsg(@RequestParam(name = "msg") String msg) throws Exception {
            return rabbitMQService.sendMsg(msg);
        }
    }

    生产者写完之后,就写消费者端的代码,消费者很简单。maven依赖,yml文件配置和生产者一样。只需要创建一个类,@RabbitListener注解写上监听队列的名称,如图所示:

    这里有个小坑,一开始RabbitMQ服务器里还没有创建队列:

    这时如果启动消费者,会报错:

    要先启动生产者,发送一条消息:

    最后再启动消费者,进行消费:

    这时候就会持续监听队列的消息,只要生产者发送一条消息到MQ,消费者就消费一条。我这里尝试发送4条:

    由于队列不存在,启动消费者报错的这个问题。最好的方法是生产者和消费者都尝试创建队列,怎么写呢,有很多方式,我这里用一个相对简单一点的:

    生产者的配置类加点东西:

    //实现BeanPostProcessor类,使用Bean的生命周期函数
    @Component
    public class DirectRabbitConfig implements BeanPostProcessor {
        //这是创建交换机和队列用的rabbitAdmin对象
        @Resource
        private RabbitAdmin rabbitAdmin;
        
        //初始化rabbitAdmin对象
        @Bean
        public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
            RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
            // 只有设置为 true,spring 才会加载 RabbitAdmin 这个类
            rabbitAdmin.setAutoStartup(true);
            return rabbitAdmin;
        }
        
        //实例化bean后,也就是Bean的后置处理器
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //创建交换机
            rabbitAdmin.declareExchange(rabbitmqDemoDirectExchange());
            //创建队列
            rabbitAdmin.declareQueue(rabbitmqDemoDirectQueue());
            return null;
        }
    }

    这样启动生产者就会自动创建交换机和队列,不用等到发送消息才创建。

    消费者需要加一点代码:

    @Component
    //使用queuesToDeclare属性,如果不存在则会创建队列
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC))
    public class RabbitDemoConsumer {
        //...省略
    }

    这样,无论生产者还是消费者先启动都不会出现问题了~

    代码地址:https://github.com/yehongzhi/mall

    五、RabbitMQ中的组成部分

    从上面的HelloWord例子中,我们大概也能体验到一些,就是RabbitMQ的组成,它是有这几部分:

    • Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。
    • Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列
    • Queue:消息队列,存储消息的队列。
    • Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
    • Consumer:消息消费者。消费队列中存储的消息。

    这些组成部分是如何协同工作的呢,大概的流程如下,请看下图:

    • 消息生产者连接到RabbitMQ Broker,创建connection,开启channel。
    • 生产者声明交换机类型、名称、是否持久化等。
    • 生产者发送消息,并指定消息是否持久化等属性和routing key。
    • exchange收到消息之后,根据routing key路由到跟当前交换机绑定的相匹配的队列里面。
    • 消费者监听接收到消息之后开始业务处理。

    六、Exchange的四种类型以及用法

    从上面的工作流程可以看出,实际上有个关键的组件Exchange,因为消息发送到RabbitMQ后首先要经过Exchange路由才能找到对应的Queue

    实际上Exchange类型有四种,根据不同的类型工作的方式也有所不同。在HelloWord例子中,我们就使用了比较简单的Direct Exchange,翻译就是直连交换机。其余三种分别是:Fanout exchange、Topic exchange、Headers exchange

    6.1 Direct Exchange

    见文知意,直连交换机意思是此交换机需要绑定一个队列,要求该消息与一个特定的路由键完全匹配。简单点说就是一对一的,点对点的发送。

    完整的代码就是上面的HelloWord的例子,不再重复代码。

    6.2 Fanout exchange

    这种类型的交换机需要将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。简单点说就是发布订阅。

    代码怎么写呢,演示一下:

    首先要先配置交换机和队列的名称:

    public class RabbitMQConfig {
        /**
         * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 A 的名称
         */
        public static final String FANOUT_EXCHANGE_QUEUE_TOPIC_A = "fanout.A";
    
        /**
         * RabbitMQ的FANOUT_EXCHANG交换机类型的队列 B 的名称
         */
        public static final String FANOUT_EXCHANGE_QUEUE_TOPIC_B = "fanout.B";
    
        /**
         * RabbitMQ的FANOUT_EXCHANG交换机类型的名称
         */
        public static final String FANOUT_EXCHANGE_DEMO_NAME = "fanout.exchange.demo.name";
    
    }

    再配置FanoutExchange类型的交换机和A、B两个队列,并且绑定。这种类型不需要配置routing key:

    @Component
    public class DirectRabbitConfig implements BeanPostProcessor {
        @Resource
        private RabbitAdmin rabbitAdmin;
        
        @Bean
        public Queue fanoutExchangeQueueA() {
            //队列A
            return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A, true, false, false);
        }
    
        @Bean
        public Queue fanoutExchangeQueueB() {
            //队列B
            return new Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_B, true, false, false);
        }
    
        @Bean
        public FanoutExchange rabbitmqDemoFanoutExchange() {
            //创建FanoutExchange类型交换机
            return new FanoutExchange(RabbitMQConfig.FANOUT_EXCHANGE_DEMO_NAME, true, false);
        }
    
        @Bean
        public Binding bindFanoutA() {
            //队列A绑定到FanoutExchange交换机
            return BindingBuilder.bind(fanoutExchangeQueueA()).to(rabbitmqDemoFanoutExchange());
        }
    
        @Bean
        public Binding bindFanoutB() {
            //队列B绑定到FanoutExchange交换机
            return BindingBuilder.bind(fanoutExchangeQueueB()).to(rabbitmqDemoFanoutExchange());
        }
        
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //启动项目即创建交换机和队列
            rabbitAdmin.declareExchange(rabbitmqDemoFanoutExchange());
            rabbitAdmin.declareQueue(fanoutExchangeQueueB());
            rabbitAdmin.declareQueue(fanoutExchangeQueueA());
            return null;
        }
    }

    创建service发布消息的方法:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Resource
        private RabbitTemplate rabbitTemplate;
        
        //发布消息
        @Override
        public String sendMsgByFanoutExchange(String msg) throws Exception {
            Map<String, Object> message = getMessage(msg);
            try {
                rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE_DEMO_NAME, "", message);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
        //组装消息体
        private Map<String, Object> getMessage(String msg) {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            String sendTime = sdf.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            return map;
        }
    }

    Controller接口:

    @RestController
    @RequestMapping("/mall/rabbitmq")
    public class RabbitMQController {
        /**
         * 发布消息
         *
         * @author java技术爱好者
         */
        @PostMapping("/publish")
        public String publish(@RequestParam(name = "msg") String msg) throws Exception {
            return rabbitMQService.sendMsgByFanoutExchange(msg);
        }
    }

    接着在消费者项目这边,创建两个队列的监听类,监听队列进行消费:

    @Component
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_A))
    public class FanoutExchangeConsumerA {
    
        @RabbitHandler
        public void process(Map<String, Object> map) {
            System.out.println("队列A收到消息:" + map.toString());
        }
    
    }
    @Component
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.FANOUT_EXCHANGE_QUEUE_TOPIC_B))
    public class FanoutExchangeConsumerB {
    
        @RabbitHandler
        public void process(Map<String, Object> map) {
            System.out.println("队列B收到消息:" + map.toString());
        }
    }

    然后启动生产者和消费者两个项目,可以看到管理界面创建了一个FanoutExchange交换机和两个队列,并且绑定了:

    使用POSTMAN进行发送消息,测试:

    然后可以看到控制台,两个队列同时都收到了相同的消息,形成了发布订阅的效果:

    6.3 Topic Exchange

    直接翻译的话叫做主题交换机,如果从用法上面翻译可能叫通配符交换机会更加贴切。这种交换机是使用通配符去匹配,路由到对应的队列。通配符有两种:"*" 、 "#"。需要注意的是通配符前面必须要加上"."符号。

    * 符号:有且只匹配一个词。比如 a.*可以匹配到"a.b"、"a.c",但是匹配不了"a.b.c"。

    # 符号:匹配一个或多个词。比如"rabbit.#"既可以匹配到"rabbit.a.b"、"rabbit.a",也可以匹配到"rabbit.a.b.c"。

    废话不多说,代码演示一下:

    依然是配置TopicExchange名称和三个队列的名称:

        /**
         * RabbitMQ的TOPIC_EXCHANGE交换机名称
         */
        public static final String TOPIC_EXCHANGE_DEMO_NAME = "topic.exchange.demo.name";
    
        /**
         * RabbitMQ的TOPIC_EXCHANGE交换机的队列A的名称
         */
        public static final String TOPIC_EXCHANGE_QUEUE_A = "topic.queue.a";
    
        /**
         * RabbitMQ的TOPIC_EXCHANGE交换机的队列B的名称
         */
        public static final String TOPIC_EXCHANGE_QUEUE_B = "topic.queue.b";
    
        /**
         * RabbitMQ的TOPIC_EXCHANGE交换机的队列C的名称
         */
        public static final String TOPIC_EXCHANGE_QUEUE_C = "topic.queue.c";

    然后还是老配方,配置交换机和队列,然后绑定,创建:

    @Component
    public class DirectRabbitConfig implements BeanPostProcessor {
        //省略...
        
        @Bean
        public TopicExchange rabbitmqDemoTopicExchange() {
            //配置TopicExchange交换机
            return new TopicExchange(RabbitMQConfig.TOPIC_EXCHANGE_DEMO_NAME, true, false);
        }
    
        @Bean
        public Queue topicExchangeQueueA() {
            //创建队列1
            return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A, true, false, false);
        }
    
        @Bean
        public Queue topicExchangeQueueB() {
            //创建队列2
            return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B, true, false, false);
        }
    
        @Bean
        public Queue topicExchangeQueueC() {
            //创建队列3
            return new Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C, true, false, false);
        }
    
        @Bean
        public Binding bindTopicA() {
            //队列A绑定到FanoutExchange交换机
            return BindingBuilder.bind(topicExchangeQueueB())
                    .to(rabbitmqDemoTopicExchange())
                    .with("a.*");
        }
    
        @Bean
        public Binding bindTopicB() {
            //队列A绑定到FanoutExchange交换机
            return BindingBuilder.bind(topicExchangeQueueC())
                    .to(rabbitmqDemoTopicExchange())
                    .with("a.*");
        }
    
        @Bean
        public Binding bindTopicC() {
            //队列A绑定到FanoutExchange交换机
            return BindingBuilder.bind(topicExchangeQueueA())
                    .to(rabbitmqDemoTopicExchange())
                    .with("rabbit.#");
        }
        
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            rabbitAdmin.declareExchange(rabbitmqDemoTopicExchange());
            rabbitAdmin.declareQueue(topicExchangeQueueA());
            rabbitAdmin.declareQueue(topicExchangeQueueB());
            rabbitAdmin.declareQueue(topicExchangeQueueC());
            return null;
        }
    }

    然后写一个发送消息的service方法:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        @Override
        public String sendMsgByTopicExchange(String msg, String routingKey) throws Exception {
            Map<String, Object> message = getMessage(msg);
            try {
                //发送消息
                rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_DEMO_NAME, routingKey, message);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
    }

    写一个Controller接口:

    @RestController
    @RequestMapping("/mall/rabbitmq")
    public class RabbitMQController {
        @Resource
        private RabbitMQService rabbitMQService;
        
        /**
         * 通配符交换机发送消息
         *
         * @author java技术爱好者
         */
        @PostMapping("/topicSend")
        public String topicSend(@RequestParam(name = "msg") String msg, @RequestParam(name = "routingKey") String routingKey) throws Exception {
            return rabbitMQService.sendMsgByTopicExchange(msg, routingKey);
        }
    }

    生产者这边写完,就写消费端,消费端比较简单,写三个监听类:

    @Component
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A))
    public class TopicExchangeConsumerA {
    
        @RabbitHandler
        public void process(Map<String, Object> map) {
            System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_A + "]收到消息:" + map.toString());
        }
    }
    
    @Component
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B))
    public class TopicExchangeConsumerB {
    
        @RabbitHandler
        public void process(Map<String, Object> map) {
            System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_B+ "]收到消息:" + map.toString());
        }
    }
    
    @Component
    @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C))
    public class TopicExchangeConsumerC {
    
        @RabbitHandler
        public void process(Map<String, Object> map) {
            System.out.println("队列[" + RabbitMQConfig.TOPIC_EXCHANGE_QUEUE_C + "]收到消息:" + map.toString());
        }
    }

    大功告成,然后启动项目开始调试。启动成功后可以看到队列和路由键绑定的关系:

    通过POSTMAN进行测试,测试一下 rabbit.# 的路由键是否能够匹配成功:

    测试成功,队列A消费到消息:

    接着测试 a.* 路由键,发送 routingKey = a.b :

    比较常用的就是以上三种:直连(DirectExchange),发布订阅(FanoutExchange),通配符(TopicExchange)。熟练运用这三种交换机类型,基本上可以解决大部分的业务场景。

    实际上稍微思考一下,可以发现通配符(TopicExchange)这种模式其实是可以达到直连(DirectExchange)和发布订阅(FanoutExchange)这两种的效果的。

    FanoutExchange不需要绑定routingKey,所以性能相对TopicExchange会好一点。

    6.4 Headers Exchange

    这种交换机用的相对没这么多。它跟上面三种有点区别,它的路由不是用routingKey进行路由匹配,而是在匹配请求头中所带的键值进行路由。如图所示:

    创建队列需要设置绑定的头部信息,有两种模式:全部匹配和部分匹配。如上图所示,交换机会根据生产者发送过来的头部信息携带的键值去匹配队列绑定的键值,路由到对应的队列。代码怎么实现呢,往下看演示代码:

    首先还是需要定义交换机名称,队列名称:

        /**
         * HEADERS_EXCHANGE交换机名称
         */
        public static final String HEADERS_EXCHANGE_DEMO_NAME = "headers.exchange.demo.name";
    
        /**
         * RabbitMQ的HEADERS_EXCHANGE交换机的队列A的名称
         */
        public static final String HEADERS_EXCHANGE_QUEUE_A = "headers.queue.a";
    
        /**
         * RabbitMQ的HEADERS_EXCHANGE交换机的队列B的名称
         */
        public static final String HEADERS_EXCHANGE_QUEUE_B = "headers.queue.b";

    然后设置交换机,队列,进行绑定:

    @Component
    public class DirectRabbitConfig implements BeanPostProcessor {
        @Bean
        public Queue headersQueueA() {
            return new Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A, true, false, false);
        }
    
        @Bean
        public Queue headersQueueB() {
            return new Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B, true, false, false);
        }
    
        @Bean
        public HeadersExchange rabbitmqDemoHeadersExchange() {
            return new HeadersExchange(RabbitMQConfig.HEADERS_EXCHANGE_DEMO_NAME, true, false);
        }
    
        @Bean
        public Binding bindHeadersA() {
            Map<String, Object> map = new HashMap<>();
            map.put("key_one", "java");
            map.put("key_two", "rabbit");
            //全匹配
            return BindingBuilder.bind(headersQueueA())
                    .to(rabbitmqDemoHeadersExchange())
                    .whereAll(map).match();
        }
    
        @Bean
        public Binding bindHeadersB() {
            Map<String, Object> map = new HashMap<>();
            map.put("headers_A", "coke");
            map.put("headers_B", "sky");
            //部分匹配
            return BindingBuilder.bind(headersQueueB())
                    .to(rabbitmqDemoHeadersExchange())
                    .whereAny(map).match();
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            rabbitAdmin.declareExchange(rabbitmqDemoHeadersExchange());
            rabbitAdmin.declareQueue(headersQueueA());
            rabbitAdmin.declareQueue(headersQueueB());
            return null;
        }
    }

    再写一个Service方法发送消息:

    @Service
    public class RabbitMQServiceImpl implements RabbitMQService {
        @Resource
        private RabbitTemplate rabbitTemplate;
        
        @Override
        public String sendMsgByHeadersExchange(String msg, Map<String, Object> map) throws Exception {
            try {
                MessageProperties messageProperties = new MessageProperties();
                //消息持久化
                messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                messageProperties.setContentType("UTF-8");
                //添加消息
                messageProperties.getHeaders().putAll(map);
                Message message = new Message(msg.getBytes(), messageProperties);
                rabbitTemplate.convertAndSend(RabbitMQConfig.HEADERS_EXCHANGE_DEMO_NAME, null, message);
                return "ok";
            } catch (Exception e) {
                e.printStackTrace();
                return "error";
            }
        }
    }

    再写一个Controller接口:

    @RestController
    @RequestMapping("/mall/rabbitmq")
    public class RabbitMQController {
        @Resource
        private RabbitMQService rabbitMQService;
        
        @PostMapping("/headersSend")
        @SuppressWarnings("unchecked")
        public String headersSend(@RequestParam(name = "msg") String msg,
                                  @RequestParam(name = "json") String json) throws Exception {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> map = mapper.readValue(json, Map.class);
            return rabbitMQService.sendMsgByHeadersExchange(msg, map);
        }
    }

    生产者这边写完了,再写两个队列的监听类进行消费:

    @Component
    public class HeadersExchangeConsumerA {
        @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A))
        public void process(Message message) throws Exception {
            MessageProperties messageProperties = message.getMessageProperties();
            String contentType = messageProperties.getContentType();
            System.out.println("队列[" + RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_A + "]收到消息:" + new String(message.getBody(), contentType));
        }
    }
    
    @Component
    public class HeadersExchangeConsumerB {
        @RabbitListener(queuesToDeclare = @Queue(RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B))
        public void process(Message message) throws Exception {
            MessageProperties messageProperties = message.getMessageProperties();
            String contentType = messageProperties.getContentType();
            System.out.println("队列[" + RabbitMQConfig.HEADERS_EXCHANGE_QUEUE_B + "]收到消息:" + new String(message.getBody(), contentType));
        }
    }

    大功告成~启动项目,打开管理界面,我们可以看到交换机绑定队列的信息:

    跟上面示意图一样~证明没有问题,一切尽在掌握之中。使用POSTMAN发送,测试全匹配的队列A:

    再测试部分匹配的队列B:

    总结

    这篇文章就先写到这里了。回顾一下学了哪些:

    • 什么是消息队列?为什么使用消息队列?
    • RabbitMQ的特点、组成部分、工作流程
    • 安装RabbitMQ,以及完成一个HelloWord小案例
    • RabbitMQ交换机的四种类型的特点,以及使用方法

    实际上RabbitMQ还有事务机制和负载均衡这些还没讲,因为篇幅实在有点长了,差不多5千字了。所以放在下期讲吧,尽请期待一下。

    上面所有例子的代码都上传github了:

    https://github.com/yehongzhi/mall

    如果你觉得这篇文章对你有用,点个赞吧~

    你的点赞是我创作的最大动力~

    想第一时间看到我更新的文章,可以微信搜索公众号「java技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!

    能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

    ]]>
    使SQL更易于阅读的几个小技巧-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800


     



    无论是数仓开发还是数据分析,写一手好的SQL是一项基本的技能。毋庸置疑,编写性能较好的SQL是非常重要的,但是,SQL的可读性同样是不容小觑的。一个有着混乱格式的SQL脚本,往往需要花费较长的时间去弄清楚脚本的具体逻辑。如果你曾经被祖传的毫无章法的SQL脚本狂虐过,你一定心有感触。本文将分享几个SQL格式的规范,当然仁者见仁智者见智,其实没有严格的标准,如果有,那就是保证易于阅读和易于维护。


    大小写保持一致


    可以对SQL关键字使用不同的大小写,但是要保持一致。看看这个:


    SELECT customer_city,count(*) from dim_customer WHERE customerProvince = '上海' Group by customer_city

    上面的SQL语句是不是很让人抓狂,大小写混用,看起来很不规范。总结起来,要注意下面几点:



    • SQL的关键字可以大写,也可以小写,但是不要大小写混用。上面的SQL查询既有完全大写,也有首字母大写,更有小写。看似是不拘小节,但是万万使不得。
    • 由于大小写是混合的,因此很难区分小写的关键字实际上是关键字还是列。此外,阅读也很烦人。
    • 字段命名要保持一致的风格,上面的SQL与中customer_city是小写加下划线,而customerProvince字段是驼峰命名法,这种不一致性显然是不可取的。

    进行一些规范之后后,查询应如下所示:


    SELECT customer_city,
       count(*)

    FROM dim_customer
    WHERE customer_province = '上海'
    GROUP BY customer_city


    使用缩进


    再来看看下面的一条查询语句:


    SELECT dp.region_name,count(*) FROM user_behavior_log ubl JOIN dim_province dp ON ubl.province = dp.province_name WHERE ubl.province = '上海市' GROUP BY dp.region_name

    将上面的SQL语句格式化下面的形式:


    SELECT dp.region_name, count(*)
    FROM user_behavior_log ubl
    JOIN dim_province dp ON ubl.province = dp.province_name
    WHERE ubl.province = '上海市'
    GROUP BY dp.region_name

    上面的格式化形式似乎清晰了很多,但是如果语句中包含了子查询、多个JOIN以及窗口函数时,同样会显得对阅读不是很友好。


    再换一种格式化方式,如下:


    SELECT
    dp.region_name, 
    count(*)

    FROM user_behavior_log ubl

    JOIN dim_province dp ON ubl.province = dp.province_name

    WHERE ubl.province = '上海市'
    GROUP BY

    dp.region_name
    

    -- 或者下面的形式
    SELECT

    dp.region_name 
    ,count(*)

    FROM user_behavior_log ubl

    JOIN dim_province dp ON ubl.province = dp.province_name

    WHERE ubl.province = '上海市'
    GROUP BY

    dp.region_name</code></pre>

    尖叫提示:对于第二种形式,在SELECT字段中,从第二个字段开始,每个字段前面添加一个逗号,而不是每个字段后面使用逗号结尾。这种方式可以很方便地识别FROM前面是否存在逗号,从而造成语法错误。当然,这个只是个人习惯问题,并不是硬性的规定。


    另外上面的SQL语句使用了4个字符缩进,当然也可以选择2个字符缩进,这个也是个人习惯问题。


    在group by 和order by之后使用字段的排列序号


    同样,这种书写风格也是个人的一种偏好,并不是一条硬性规定。应该有很多的初学者对此种写法并不是很清楚。


    看下面的这条SQL:


    SELECT
    dp.region_name, 
    dp.province_name,
    count(*)

    FROM user_behavior_log ubl

    JOIN dim_province dp ON ubl.province = dp.province_name

    GROUP BY

    dp.region_name,
    dp.province_name

    ORDER BY

    count(*) desc -- Hive不支持</code></pre>

    可以写成下面的形式:


    -- 注意:MySQL、Impala支持这种写法,Hive不支持

    SELECT

    dp.region_name, 
    dp.province_name,
    count(*)

    FROM user_behavior_log ubl

    JOIN dim_province dp ON ubl.province = dp.province_name

    GROUP BY 1,2
    ORDER BY 3


    这样写有如下的好处:



    可以节省行:通过许多字段进行分组不仅会在SELECT子句中添加更多行,还会在GROUP BY和ORDER BY子句中添加更多行,甚至可能使查询中的行数增加一倍。
    可维护性:如果想改变分组字段,只需在SELECT子句中进行操作,在GROUP BY语句中不需要修改。
    方便:只需要GROUP BY 1,2,3,…,n,其中n为分组列的字段序号。

    使用Common Table表达式(with语句)


    该方式称之为Common Table Expressions(CTE),用来简化复杂查询。它们可以定义为临时视图,因为它们仅在整个查询执行期间存在。


    看一个简单的例子:


    -- 注意Hive、Impala支持这种语法,低版本的MySQL不支持(高版本支持)
    WITH employee_by_title_count AS (
    SELECT
        t.name as job_title
        , COUNT(e.id) as amount_of_employees
    FROM employees e
        JOIN job_titles t on e.job_title_id = t.id
    GROUP BY 1

    ),
    salaries_by_title AS (

     SELECT
         name as job_title
         , salary
     FROM job_titles

    )
    SELECT *
    FROM employee_by_title_count e

    JOIN salaries_by_title s ON s.job_title = e.job_title</code></pre>

    上面的语句中,最终的查询使用employee_by_title和salaries_by_title的两个结果集进行JOIN产生最终结果。这比在SELECT子句中或直接在FROM子句中进行子查询更具可读性和可维护性。


    使用具有描述性的别名


    这一点非常重要,如果查询的列字段很多,肯能会存在一些id,count(*)等,很难辨识代表什么含义,所以需要为每个查询列加上可读的、易于理解的别名,能够让其他人一眼就能看出代表什么含义,这样可以增加脚本的可维护性。


    总结


    文中提到的一些规范有些是必须要遵守的,有些是个人的编码习惯,无论你是开发人员、数据分析师、数仓开发,遵循一些规范可以避免不必要的麻烦。值得注意的是,关于SQL的格式,没有一个标准的约定,需要与团队的其他成员达成共识,一起按照相同的约定进行开发,从而可以大大提高脚本的可读性和可维护性。





    本文作者:西贝木土



    ]]>
    【最佳实践】实时计算 Flink 版在金融行业的实时数仓建设实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 行业背景
    • 行业现状: 

    金融是现代经济的核心。我国金融业在市场化改革和对外开放中不断发展,金融总量大幅增长。金融稳定直接关系到国家经济发展的前途和命运,金融业是国民经济发展的晴雨表。对我国金融业发展现状进行客观分析,对金融业发展趋势进行探索,有助于消除金融隐患,使金融业朝着健康、有序方向发展。

    • 大数据在其行业中的作用:

      1. 金融服务和产品创新:借助社交网络等平台产生的海量用户和数据记录着用户群体的兴趣和偏好情绪等信息, 对客户行为模式进行分析,可以带来更贴近客户需求的产品创新。
      2. 增强用户体验:通过大数据分析对客户进行画像,结合客户画像特征,为用户提供个性化服务,增强用户体验。

    业务场景

    某保险公司开发了个金融类APP,该公司在APP中会投放保险广告、发布优惠活动,用户通过APP进行投保等操作。
    业务的构建涉及到几个端:

    1. APP:应用程序,用户访问入口,用户通过APP点击浏览保险广告、优惠活动等,并进行投保下单。
    2. 后台系统:

    a.运营人员:
    (1)根据用户提交的订单统计指定时间段的总投保人数和总投保金额,辅助优化运营方案。
    (2)对用户的日常行为做出分析,分析出每个用户比较关注的信息,作为推荐系统的数据来源。
    b:业务经理:
    对重点客户的投保金额变动进行监控,将投保金额变动较大的重点客户推送给业务经理,业务经理针对性开展客户挽留等操作。

    技术架构

    image.png
    架构解析:
    数据采集:该场景中,数仓的数据主要来源于APP等系统的埋点信息,被实时采集至DATAHUB作为Flink的输入数据。
    实时数仓架构:该场景中,整个实时数仓的ETL和BI部分的构建,全部通过Flink完成,Flink实时读取DATAHUB的数据进行处理,并与维表进行关联查询等操作,最终实时统计的结果输入到下游数据库RDS中。

    业务指标

    • 运营数据分析

      • 每小时的投保人数
      • 每小时的总保费
      • 每小时总保单数
    • 用户行为监控

      • 用户原投保金额
      • 用户现投保金额
    • 用户行为分析

      • 用户最后访问的页面类型
      • 用户最后访问的页面地址

    数据结构

    场景一:运营数据分析

    本场景用于计算每小时总投保人数和总保费。
    用户投保会生成一份订单,订单内容包括用户id、用户姓名、订单号等。flink实时读取订单信息,用where过滤出大于当前小时时间段的数据(数据过滤),然后根据用户id做分组用last_value函数获取每个用户最终生成的订单信息(订单去重),最后按照小时维度聚合统计当前小时的总保费和总投保人数。

    输入表

    CREATE   TABLE  user_order
    (
        id                          bigint    comment '用户id'
        ,order_no                    varchar    comment '订单号'
        ,order_type                  bigint    comment '订单类型'
        ,pay_time                    bigint  comment '支付时间'
        ,order_price                 double    comment '订单价格'
        ,customer_name               varchar    comment '用户姓名'
        ,customer_tel                varchar    comment '用户电话'
        ,certificate_no              varchar    comment '证件号码'
        ,gmt_created                 bigint  comment '创建时间'
        ,gmt_modified                bigint  comment '修改时间'
        ,account_payble             double      comment '应付金额'
    
    ) WITH (
           type='datahub',
         topic='user_order'
           ...
    )

    输出表

    CREATE    TABLE hs_order (
        biz_date              varchar COMMENT 'yyyymmddhh'
        ,total_premium         DOUBLE COMMENT '总保费'
        ,policy_cnt            BIGINT COMMENT '保单数'
        ,policy_holder_cnt     BIGINT COMMENT '投保人数'
        ,PRIMARY KEY (biz_date)
    ) WITH
     (
       type='rds',
       tableName='adm_pfm_zy_order_gmv_msx_hs'
       ...
     ) 
     ;

    业务代码

    1:数据清洗

    create view  last_order
    as 
    select 
         id                                 as id               
        ,last_value(order_no)               as order_no                   
        ,last_value(customer_tel)           as customer_tel     
        ,last_value(gmt_modified)           as gmt_modified                      
        ,last_value(account_payble)         as account_payble   
        from user_order
        where gmt_modified  >= cast(UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(), 'yyyy-MM-dd'), 'yyyy-MM-dd')*1000 as bigint)
        group by id
    ;

    2:数据汇总

    insert into hs_order
    select 
    biz_date
    ,cast (total_premium as double) as total_premium
    ,cast (policy_cnt as bigint) as policy_cnt
    ,cast (policy_holder_cnt as bigint) as policy_holder_cnt
    from (
    select 
        from_unixtime(cast(gmt_modified/1000 as bigint),'yyyyMMddHH')      as biz_date
        ,sum(coalesce(account_payble,0))  as total_premium
        ,count(distinct order_no)   as policy_cnt
        ,count(distinct customer_tel)  as policy_holder_cnt
    from  last_order a 
    group by 
    from_unixtime(cast(gmt_modified/1000 as bigint),'yyyyMMddHH')
    )a 
    ;

    场景二:用户行为监控

    本场景对投保金额变动较大(总保额变动大于15%)的重点用户进行监控。
    Flink实时读取用户新建订单数据,新建订单包括用户的id和现投保金额,通过where过滤没有保存成功的订单。维表中存储了业务经理关注的重点用户数据(如原投保金额),通过新建订单中的用户id与维表进行关联查询,如果维表中存在此用户且总保额下降15%以上,则将该用户的详细信息推送给业务经理,并且在维表中更新该用户投保金额及投保信息。

    输入表

    create table update_info
    (
     id             bigint      comment '用户id'
    ,channel        varchar     comment '渠道编号'
    ,open_id        varchar     comment '订单id'
    ,event          varchar     comment '事件类型'
    ,now_premium  varchar     comment '现投保金额'
    ,creator        varchar     comment '创建人'
    ,modifier       varchar     comment '最后修改人'
    ,gmt_modified   bigint      comment '修改时间'
    ,now_info       varchar     comment '现投保信息'
    ) with (
        type = 'datahub',
        topic = 'dh_prd_dm_account_wechat_trace'
        ...
       
    );

    维表

     create table raw_info
    (
         id                 bigint  comment '用户id'
        ,raw_premium      varchar  comment '原投保金额'
        ,raw_info           varchar  comment '原投保信息'
        ,PRIMARY KEY(id)
        ,PERIOD FOR SYSTEM_TIME
    ) WITH (
        type='ots',
        tableName='adm_zy_acct_sub_wechat_list'
        ...
    );
    

    输出表

    create table update_info
    (
         id               bigint  comment '用户id'
        ,raw_info         varchar comment '原投保信息'
        ,now_info         varchar comment '现投保信息'
        ,raw_premium      varchar comment '原投保金额'
        ,now_premium      varchar comment '现投保金额'
        ,PRIMARY KEY(id)
    ) WITH (
        type='rds',
        tableName='wechat_activity_user'
        ...
    );
    

    业务代码:

    create view info_join as 
    select
          t1.id               as  id
        ,t2.raw_info          as  raw_info
        ,t1.now_info          as  now_info  
        ,t2.raw_premium     as raw_premium
        ,t1.now_premium     as now_premium
    from update_info t1
    inner join raw_info FOR SYSTEM_TIME AS OF PROCTIME() as t2
    on t1.id = t2.id ;
    insert into update_info
    select 
         id                        as id  
        ,raw_info                  as raw_info
        ,now_info                  as now_info
        ,raw_premium               as raw_premium  
        ,now_premium               as now_premium  
    from info_join where now_premium<raw_premium*0.85
    ;
    insert into raw_info
    select 
         id                        as id  
        ,now_premium               as raw_premium  
        ,now_info                  as raw_info
    from info_join
    ;

    场景三:用户行为分析

    本场景记录用户最后访问的页面名称和类型,作为用户画像的特征值。
    Flink读取用户浏览APP页面的日志信息,如用户id、页面名称和页面类型等信息,根据用户id和设备id进行分组,通过last_value函数获取用户最后一次访问页面的名称和类型,输出到RDS作为推荐系统的输入数据,在下次用户登录的时候为其推送相关广告信息,提升用户广告点击率和下单的成功率。

    输入表

    
    create table user_log
    (
     log_time                bigint  comment '日志采集日期(Linux时间)' 
    ,device_id               varchar  comment '设备id'
    ,account_id              varchar  comment '账户id'
    ,bury_point_type         varchar  comment '页面类型或埋点类型'
    ,page_name               varchar  comment '页面名称或埋点时一级目录'
    ) WITH (
        type = 'datahub',
        topic = 'edw_zy_evt_bury_point_log'
        ...
    );
    

    输出表

    create table user_last_log
    (
         account_id         varchar  comment 'account_id'
        ,device_id          varchar    comment  '设备id'
        ,bury_point_type    varchar  comment '页面类型'
        ,page_name          varchar  comment '页面名称'
        ,primary key(account_id)
    ) WITH (
        type='rds',
        tableName='adm_zy_moblie_charge_exchg_rs'
        ...
        
    );
    

    业务代码

    
    insert into user_last_log
    select
        account_id
        ,device_id
        ,last_value(bury_point_type)  as bury_point_type
        ,last_value(page_name)  as page_name
    from user_log
    where account_id is not null 
    group by account_id,device_id
    
    ]]>
    2000万市民报名!成都“消费券”活动如何顺利完成多端多平台海量报名数据的处理?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 huagai_VCG21gic19918165_RF_2M.jpg

    成都信通信息技术有限公司(简称“成都信通”或“信通公司”)创立于2015年12月,是成都产业投资集团下属的国有控股科技公司,注册资本为3000万元。成都信通信息技术有限公司重点聚焦在金融科技、产业数字化、产业互联网、网信服务等领域深耕发展。
    随着公司业务的迅猛发展,成都信通对于流量防护、弹性伸缩、服务监控、版本发布等方面提出了新的诉求。以消费券活动报名场景为例,在流量防护方面,面对突发流量、高并发场景,需要保证业务系统能够持续平稳的运行。在弹性伸缩方面,高并发、大流量的情况下需要系统平稳快速的水平扩容,从而保证业务系统的稳定性。在服务监控方面,系统的监控需要做到不仅可以了解实时的系统指标,同时需要及时告警并发现生产隐性Bug。最后,在版本发布方面,版本迭代不会使得业务中断、能够做到无损上下线。
    针对以上诉求,在与阿里云技术专家深入讨论后,成都信通决定采用阿里云企业级分布式应用服务EDAS。EDAS是一站式微服务应用托管平台,对开源Spring Cloud和Dubbo无侵入支持,零成本迁移。同时提供开源所不具有的企业级能力。

    image.png

    阿里云EDAS的动态扩缩容、灰度发布等能力,为成都信通提供了一整套的应用生命周期的管理。在此之前,从业务代码开发完成到部署到生产环境以及微服务整个生命周期的管理一直是一个比较棘手的问题,成都信通采用EDAS后,可以将整个应用的生命周期全部托管到EDAS上,真正做到一键部署、一键发布。同时EDAS平台对应用快速扩缩容以及对流量控制、熔断、降级等功能的支持,很好地帮助成都信通在面对大流量、高并发场景时,业务系统依旧能够持续平稳的运行。
    依托于EDAS灰度发布、无损上下线功能,保证了成都信通在业务不中断的情况下,持续快速的发布新业务功能。同时,借助于EDAS整合的阿里云应用监控服务,成都信通微服务体系获得了更好的监控能力。通过链路追踪、慢SQL、问题诊断等一系列技术手段,成都信通可以及时定位并解决生产隐性Bug。此外,SLB+后端服务器的高可用架构满足业务高并发需求的同时,避免了业务出现单点故障。
    成都市消费券系统构建活动报名的高并发应用场景,利用EDAS强大的流量管理能力、运维服务监控及应用弹性扩容管理等功能,实现了满足成都市2000万市民的报名业务场景,很好地支撑了多端多平台的海量报名数据的接收。2020年第二阶段消费券活动期间,使用EDAS上服务调用量平稳度过了比正常流量高百倍的峰值。

    ]]>
    初识AliOS Things |《AliOS Things快速开发指南》-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 初识AliOS Things

    AliOS Things快速入门帮助您初识AliOS Things系统,并实现从零开始的一个入门操作。

    什么是AliOS Things

    AliOS Things发布于2017年杭州云栖大会,是AliOS家族旗下,面向IoT领域的高可伸缩物联网操作系统。
    image.png
    AliOS Things致力于搭建云端一体化IoT基础设施,具备极致性能、极简开发、云端一体、丰富组件、安全防护等关键能力。AliOS Things 支持多种多样的设备连接到阿里云IoT平台,可广泛应用在智能家居、智慧城市、工业,新出行等领域。
    关键特性

    • 即插即用的连接和丰富的服务。

      • 支持uMesh即插即用网络技术,设备上电自动连网。
    • 差分+安全OTA升级。

      • 差分增量包升级。
      • 安全数字签名。
      • 安全下载通道。
      • 断点续传。
      • 乒乓升级。
      • 版本回溯。
    • 全面彻底的安全保护。

      • 提供系统和芯片级别安全保护。
      • 支持可信运行环境(支持ARMV8-M Trust Zone)。
      • 支持预置ID2根身份证和非对称密钥以及基于ID2的可信连接和服务。
    • 高度优化的性能。

      • 内核支持Idle Task,内存资源消耗低,RAM小于1 KB,ROM小于2 KB,提供硬实时能力。
      • 提供Yloop事件框架以及基于此整合的核心组件,避免栈空间消耗,核心架构良好支持极小footprint的设备。
    • 极简开发

      • 基于Linux之上的轻量虚拟化环境,提供在Linux平台上开发与硬件无关的IoT应用和软件库,使用GDB、Valgrind、SystemTap等PC平台工具诊断开发问题。
      • 提供IDE,支持系统、内核行为Trace, Mesh组网图形化显示。
      • 提供Shell交互,支持内存踩踏、泄露、最大栈深度等各类侦测。
      • 提供面向组件的编译系统以及aos-cube工具,支持灵活组合IoT产品软件栈。
      • 提供包括存储(掉电保护、负载均衡)在内的各类产品级别的组件。

    两大开发流程

    AliOS Things编译完成后,可以使用线上或者线下开发板烧录。调试完成后,即可应用到您的实际业务中。 开发板开发流程图如下:
    image.png

    开发板类型 流程说明 适用场景
    线下开发板 1、安装开发环境2、项目编译3、固件烧录4、调试 真实开发环境。
    线上开发板 开发的流程与上述使用线下开发板的流程基本一致。主要的区别在于固件烧录前,需要先申请线上开发板,操作步骤请参见使用线上开发板进行开发调试。 线上体验。线上开发流程主要适合当您手上没有现成可用的实体开发板时,可以使用线上的开发板来调试验证您的程序。说明: 在实际开发中,您仍然需要使用线下开发板进行开发。

    下一篇:AliOS Things开发前准备

    ]]>
    1.1 了解Spring框架 -《SSM深入解析与项目实战》-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 声明

    专栏链接:https://chenhx.blog.csdn.net/category_10263372.html
    作者:谙忆

    1.1 了解Spring框架 -《SSM深入解析与项目实战》

    项目中所有的源码都可以在此链接的仓库中找到:https://github.com/chenhaoxiang/uifuture-ssm  

    本章非常形象的介绍了Springn中最核心的两个概念,IoC和AOP,使用了现实中的例子进行讲解,让开发者更加容易理解。  

    第1篇  基础概念

    第1章  谈谈SSM框架和Redis

    本章节对于Spring、Spring MVC、MyBatis和Redis进行了一些简单的介绍,不会对技术有太过深入的讲解。

    1.1  了解Spring框架

    Spring是一个轻量级的企业级Java开发开源框架,是为了解决企业项目开发的复杂性而创建的。学习Spring,最重要的就是要理解两个核心概念,即IoC(Inversion of Control 控制反转)和AOP(Aspect Oriented Programing面向切面编程)。

    Spring之所以在企业中应用如此广泛,完全是由于它先进的设计理论:

    分层架构,使用者只用引入自己所需要的某一个组件,不需要再做一些重复性的工作,简化开发与配置。对于项目没有太多的入侵性,在使用Spring框架的时候,并不需要继承或者实现框架中的类和接口。
    使用IoC容器进行实例化对象以及管理对象间的依赖,面向接口编程,降低模块与模块之间的耦合,方便扩展。

    ......
    更多内容请查看:
    https://chenhx.blog.csdn.net/article/details/107795555

    ]]>
    申通的云原生实践之路:如何实现应用基于容器的微服务改造?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 头图.png

    随着云计算的普及与云原生的广泛应用,越来越多的从业者、决策者清晰地认识到「云原生化将成为 企业技术创新的关键要素,也是完成企业数字化转型的最短路径」

    因此,具有前瞻思维的互联网企业从应用诞生之初就扎根于云端,谨慎稳重的新零售、政府、金融、医疗等领域的企业与机构也逐渐将业务应用迁移上云,深度使用云原生技术与云原生架构。面对架构设计、开发方式到部署运维等不同业务场景,基于云原生架构的应用通常针对云的技术特性进行技术生命周期设计,最大限度利用云平台的弹性、分布式、自助、按需等产品优势。

    作为发展最为迅猛的物流企业之一,申通快递一直积极探索技术创新赋能商业增长之路,以期达到降本提效目的。目前,申通快递日订单处理量已达千万量级,亿级别物流轨迹处理量,每天产生数据已达到 TB 级别,使用 1300+ 个计算节点来实时处理业务。

    当业务飞速发展遭遇运维瓶颈

    过往申通快递的核心业务应用运行在 IDC 机房,原有 IDC 系统帮助申通安稳度过早期业务快速发展期。但伴随着业务体量指数级增长,业务形式愈发多元化。原有系统暴露出不少问题,传统 IOE 架构、各系统架构的不规范、 稳定性、研发效率都限制了业务高速发展的可能。软件交付周期过长,大促保障对资源的特殊要求难实现、系统稳定性难以保障等业务问题逐渐暴露。

    在与阿里云进行多次需求沟通与技术验证后,申通最终确定阿里云为唯一合作伙伴,采用云原生技术和架构实现核心业务搬迁上阿里云。2019 年开始将业务逐步从 IDC 迁移至阿里云。目前,核心业务系统已经在阿里云上完成流量承接,为申通提供稳定而高效的计算能力。

    全面改造云原生升级,助力业务发展

    申通核心业务系统原架构基于 Vmware+Oracle 数据库进行搭建。随着搬迁上阿里云,架构全面转型为基于 Kubernetes 的云原生架构体系。其中,引入云原生数据库并完成应用基于容器的微服务改造是整个应用服务架构重构的关键点。

    • 引入云原生数据库

    通过引入 OLTP 跟 OLAP 型数据库,将在线数据与离线分析逻辑拆分到两种数据库中,改变此前完全依赖 Oracle 数据库的现状。满足在处理历史数据查询场景下 Oracle 数据库所无法支持的实际业务需求。

    • 应用容器化

    伴随着容器化技术的引进,通过应用容器化有效解决了环境不一致的问题,确保应用在开发、测试、生产环 境的一致性。与虚拟机相比,容器化提供了效率与速度的双重提升,让应用更适合微服务场景,有效提升产研效率。

    • 微服务改造

    由于过往很多业务是基于 Oracle 的存储过程及触发器完成的,系统间的服务依赖也需要 Oracle 数据库 OGG 同步完成。这样带来的问题就是系统维护难度高且稳定性差。通过引入 Kubernetes 的服务发现,组建微服务解决方案,将业务按业务域进行拆分,让整个系统更易于维护。

    综合考虑申通实际业务需求与技术特征,最终选择了「阿里云 ACK+ 神龙 + 云数据库」的云原生解决方案,从而实现核心应用迁移上阿里云。

    1.png

    1. 架构阐述

    基础设施,全部计算资源取自阿里云的神龙裸金属服务器。相较于一般云服务器(ECS),Kubernetes 搭配神龙服务器能够获得更优性能及更合理的资源利用率且云上资源按需取量,对于拥有大促活动等短期大流量业务场景的申通而言极为重要。相较于线下自建机房、常备机器,云上资源随取随用。在大促活动结束后,云上资源使用完毕后即可释放,管理与采购成本更低,相应效率。

    流量接入,阿里云提供两套流量接入,一套是面向公网请求,另外一套是服务内部调用。域名解析采用云 DNS 及 PrivateZone。借助 Kubernetes 的 Ingress 能力实现统一的域名转发,以节省公网 SLB 的数量,提高运维管理效率。

    2. 平台层

    基于 Kubernetes 打造的云原生 PaaS 平台优势明显突出。

    打通 DevOps 闭环,统一测试,集成,预发、生产环境; 天生资源隔离,机器资源利用率高; 流量接入可实现精细化管理; 集成了日志、链路诊断、Metrics 平台;

    统一 ApiServer 接口和扩展,天生支持多云跟混合云部署。

    3. 应用服务层

    每个应用都在 Kubernetes 上面创建单独的一个 Namespace,应用跟应用之间实现资源隔离。通过定义各个 应用的配置 Yaml 模板,当应用在部署时直接编辑其中的镜像版本即可快速完成版本升级,当需要回滚时直接在本地启动历史版本的镜像快速回滚。

    4. 运维管理

    线上 Kubernetes 集群采用阿里云托管版容器服务,免去了运维 Master 节点的工作,只需要制定 Worker 节点上线及下线流程即可。同时业务系统均通过阿里云的 PaaS 平台完成业务日志搜索,按照业务需求投交扩容任务,系统自动完成扩容操作,降低了直接操作 Kubernetes 集群带来的业务风险。

    全面释放云原生技术红利

    成本方面:使用公有云作为计算平台,可以让企业不必因为业务突发增长需求,而一次性投入大量资金成本用于采购服务器及扩充机柜。在公共云上可以做到随用随付,对于一些创新业务想做技术调研十分便捷。用完即释放, 按量付费。另外云产品都免运维自行托管在云端,有效节省人工运维成本,让企业更专注于核心业务。

    稳定性方面:首先,云上产品提供至少 5 个 9 以上的 SLA 服务确保系统稳定,而自建系统稳定性相去甚远。其次,部分开源软件可能存在功能 bug,造成故障隐患。最后,在数据安全方面云上数据可以轻松实现异地备份,阿里云数据存储体系下的归档存储产品具备高可靠、低成本、安全性、存储无限等特点,让企业数据更安全。

    效率方面:借助与云产品深度集成,研发人员可以完成一站式研发、运维工作。从业务需求立项到拉取分支开发, 再到测试环境功能回归验证,最终部署到预发验证及上线,整个持续集成流程耗时可缩短至分钟级。排查问题方面,研发人员直接选择所负责的应用,并通过集成的 SLS 日志控制台快速检索程序的异常日志进行问题定位,免去了登录机器查日志的麻烦。

    赋能业务:阿里云提供超过 300 余种的云上组件,组件涵盖计算、AI、大数据、IOT 等等诸多领域。研发人员开箱即用,有效节省业务创新带来的技术成本。

    想要了解更多阿里云云原生技术细节,以及更多企业实践案例?那么就快下载《云原生架构白皮书》吧!点击链接即可下载:https://developer.aliyun.com/topic/cn-architecture-paper

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    阿里研究员:软件测试中的18个难题-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png
    十多年前我在上一家公司的时候看到过内部有个网站有一个Hard Problems in Test的列表,上面大概有三四十个问题的样子,是各个部门的测试同学提供的。但可惜后来那个list失传了,我很后悔自己当时没有保存一份。后来很多次我都想要找到那份list,因为上面列的那些问题指出了测试专业在自身专业性上的巨大发展空间。那份list上的问题让当时的我相信,软件测试这件事情本身的难度一点都不亚于软件开发,甚至可能更难一点。

    如果今天要重建这么一份Hard Problems in Test列表,下面这些问题是我会加到这份列表上的[1]。

    一 测试充分度(Test Sufficiency)

    如何回答“测够了吗“(包括测新和测旧)。代码覆盖率是衡量测试充分性的起点,但远远不是终点。要回答”测够了吗“,至少还要考虑是否测了所有的场景、所有的状态、所有的状态转移路径、所有的事件序列、所有可能的配置、所有可能的数据等等等等。即便如此,我们可能还是无法100%确信我们已经测够了。可能我们最终只能做到非常趋近于测够了[2]。

    二 测试有效性(Test Effectiveness)

    如何评价一组测试用例的发现bug的能力。有效性(发现bug的能力)和充分性(测够了没有)是两个正交的属性。评价测试用例有效性可以通过正向的分析进行,例如,分析测试用例是否校验了所有在测试过程中SUT落库的数据。更具有通用性的做法是变异测试(Mutation Testing),即在被测代码里注入不同的“人造bug”,统计多少能被测试用例感知到。目前变异测试我们已经有工程化规模化的落地了,后续的工作重点有:1)如何防止钝化(或曰“杀虫剂效应”),2)不但对被测代码进行注入,还能对配置、数据等进行更全面的注入。

    三 测试用例瘦身

    以前广告行业有句话:我知道广告费有一半是浪费掉的,但不知道哪一半是浪费掉的[3]。

    软件测试也有类似的困惑:那么多用例,要花那么多时间去跑,我知道这里面有很多时间是浪费掉的,但我不知道哪些时间是浪费掉的。浪费的形式包括:

    • 冗余步骤:有些是浪费在一些重复的步骤上,每个用例都要去做一些类似的数据准备,每个用例都要去执行一些中间过程(这样才能推进到下一步)。
    • 等价类:一个支付场景,我要不要在所有的国家、所有的币种、所有的商户、所有的支付渠道和卡组的排列组合都测一遍?这么测,代价太高。不这么测,我担心可能某个特定商户在某个特定国家有个特定逻辑我就漏掉了。对于具体的业务,还可以进行人肉分析。有没有更通用的、而且比较完备和可靠的等价类分析的技术手段?
    • 我有N个用例,我猜这N个用例里面可能存在M个用例,即使删掉这M个用例,剩下的N-M个用例的效果和之前N个用例的效果一样。如何识别是否存在这样的M个用例、如果存在的话是哪M个。

    我参加过内部一场质量线晋升到P9的评审,当时有个评委问了那位同学一个问题:“那么多测试用例,以后你怎么删”。这个问题看似简单,其实非常难。我觉得,从原理上来说,如果测试充分度和测试有效性的度量都做的非常好了、度量成本非常低了,我们是可以通过大量的不断的尝试来删用例的。这是一种工程化的思路,也许还有其他的理论推导的思路。

    四 测试分层

    很多团队都会纠结到底要不要做全链路回归、做到什么程度。这个问题的核心点就是:有没有可能、有没有一种做法,只要把系统间的边界约定的足够好足够完整,就可以做到在改动一个系统的代码后,不需要和上下游系统进行集成测试,只要按照边界约定验证好自己的代码就可以确保没有任何regression了。

    包括我在内的很多人相信那是可能的,但既无法证明,也不敢在实操中就完全不跑集成。我们也缺乏可以完全复制的成功经验,缺乏一套完整的方法论指导开发团队和QA团队要怎么做就可以达到回归无需集成上下游。

    有时候,我觉得我现在就像是哥德堡的市民,不断的走啊走,尝试找出一条一次性不重复的走过那7座桥的路线。但也许就有那么一天,有一个像欧拉那样的人会出现在我面前,用理论证明告诉我,那是不可能的。

    五 减少分析遗漏

    分析遗漏是很多故障的原因。开发做系分的时候,有一个corner case没考虑到、没有处理。测试做测分的时候,忘记考虑某个特殊场景了。兼容性评估,评估下来没有兼容性问题的,但结果是有的。而且很多时候,分析遗漏属于unknown unknowns,我压根就不知道我不知道。有没有一套方法和技术,可以减少分析遗漏,可以把unknown unknowns转化为knowns?

    六 用例自动生成

    Fuzz Test、Model Based Test、录制回放、Traffic Bifurcation(引流)等都是自动生成用例的手段。有些已经比较成熟(例如单系统的录制回放、引流),有些多个团队都在探索(例如Fuzz),有些则一直没有大规模的成功实践(例如MBT)。我们也有过探索如何从PRD里通过NLP来生成用例。用例自动生成中,有时候难点还不是生成test steps,难度反而是怎么生成test oracle。Anyway,测试用例自动生成是一个非常大的领域,这个方向上未来可以做的还非常多。

    七 问题自动排查

    包括线上和线下。对于比较初级的问题,自动排查方案往往有两个局限性。首先,方案不够通用,多多少少比较定制化。其次,比较依赖人工积累规则(说的好听点叫“专家经验”),主要是通过记录和重复人肉排查的步骤来实现。然而,每个问题都不完全一样,问题稍微一变,之前的排查步骤可能就不work了。现在有一些技术,比如调用链路的自动比对,对排查问题和缺陷自动定位很有帮助。

    八 缺陷自动修复

    阿里的Precfix、Facebook的SapFix等是目前比较知名的一些工业界的做法。但总的来说,现有的技术方案,都有这样那样的局限性和不足,这个领域还在相对早期阶段,后面的路还很长。

    九 测试数据准备

    测试用例的一个重要设计原则是:测试用例之间不应该有依赖关系,一个测试用例的执行结果不应该受到其他测试用例的执行结果(包括是否执行)的影响。基于这个原则,传统的最佳时间是确保每个测试用例都应该是自给自足的:一个用例需要触发的后台处理流程应该由这个用例自己来触发,一个测试用例需要的测试数据应该自己来准备,等等。但如果每个用例所需要用到的测试数据都是自己来从头准备的,执行效率就比较低。怎么既不违背“测试用例之间不应该有依赖关系”的大原则,又能减少测试数据的准备时间?

    我设想的是一种更加完备的数据银行。每个测试用例执行完后,都会把它自己产生的数据交给数据银行,例如,一个在某个特定国家的已经通过KYC、已经绑了一张卡的会员,一笔已经支付成功的交易,一个已经完成入驻签约流程的商户。下一个测试用例开始的时候,会先问一下数据银行:“我要一个满足这样这样条件的商户,你有没有”。上个用例跑出来的那个商户正好符合条件,数据银行就会把商户“借”给这个用例用。而且一旦借出,直到被归还前,这个商户不会被借给其他用例。

    经过一段时间的运行,数据银行能够学习到每个测试用例需要什么样的数据、以及会产生什么样的数据。这个知识是通过学习得到的,不需要人肉去添加描述,所以也能适用于老系统的存量用例。有了这个知识,数据银行可以实现两个优化:

    • 一次测试执行批次开始后,数据银行会看到这个批次中后面那些用例需要什么样的数据,提前先准备起来。这样,等执行到那些用例的时候,数据银行里就已经有符合条件的数据准备好了。
    • 根据每个测试用例需要什么样的数据、以及会产生什么样的数据,数据银行可以合理的编排测试用例的执行先后次序,最大化的实现测试数据的复用,减少测试数据的量和准备开销。

    测试银行把测试数据“借”给用例的时候,可以有多种不同的模式。可以是独占(exclusive)的,也可以是共享的。共享的也可以指定共享读、共享写、还是都只读不能写(例如,一个商户可以被多个用例用来测试下单支付结算场景,但这些用例都不可以去修改这个商户本身,例如重新签约)。

    如果把开关、定时任务等resource也作为一种广义的测试数据由数据银行来管理,能实现测试用例尽可能并行执行。例如,有N个用例都需要修改一个开关值,这N个用例如果并行执行的话就会相互影响,他们相互之间应该串行执行。但N个用例中的任何一个,都可以和这N个用例之外的用例并行执行。数据银行掌握了每个用例对各种资源的使用模式的详细情况,再加上每个用例的平均运行时间等数据,就可以最优化、最准确的对一批测试用例进行编排,做到可以并行的都尽可能并行、不能并行的确保不并行,而且还可以在一个批次的执行过程中不断的调整余下还未执行的用例的编排。

    这样一个数据银行是普遍适用的,不同业务之间的差异无非是具体的业务对象和resource不一样。这些差异可以通过插件形式实现。如果有这么一个通用的数据银行[4],可以很方便的adopt,大量的中小软件团队的测试效率都可以得到明显的提高。这样的一个更加完备的数据银行的想法,我到目前为止还只是想法,一直没有机会实践。

    十 异常测试

    一个分布式系统,它的内部、内部各部分之间以及它和外部的交互都会出现各种异常:访问超时、网络连接和耗时的抖动、连接断开、DNS无法解析、磁盘/CPU/内存/连接池等资源耗尽等等。如何确保系统的行为(包括业务逻辑、以及系统自保护措施如降级熔断等)在所有的情况下都是符合预期的?今天我们的线上演练(本质上也是一种异常测试))已经做了很多了。如何把更多的问题提前到线下来发现?对于一个复杂的分布式系统来说,要遍历所有可能出现异常的地方和所有可能出现的异常,异常用例的数量是非常大的。此外,某些异常情况下,系统对外表现出来的行为应该没有变化;而另一些异常情况下,系统行为是会有变化的。对于后一类,如何给出每一个异常用例的预期结果(即test oracle),也是比较有难度的。

    十一 并发测试(Concurrency Test)

    并发(concurrency)可能出现在各个level:数据库层面,对同一张表、同一条记录的并发读写;单系统层面,同一个进程内的多个线程之间的并发,单服务器上的多个进程之间的并发,以及单个服务的多个实例之间的并发;业务层面,对同一个业务对象(会员、单据、账户等)的并发操作,等等。传统的并发测试是基于性能测试来做的,有点靠撞大运,而且经常是即便跑出问题来了也会被忽视或者无法repro。并发测试领域,我接触过的一些成果包括Microsoft的CHESS以及阿里的谭锦发同学在探索的分布式模型检查&SST搜索算法。

    十二 回滚的测试

    安全生产三板斧宣传了多年,在阿里经济体内大家都能做到“可回滚”了。但我所观察到的是:很多时候我们有回滚的能力,但是对回滚后系统的正确性,事前保障的手段还不够。我们更多的是靠灰度和监控等事后手段来确保回滚不会回滚出问题来。事实上,过去两年,我自己已经亲身经历过好几次回滚导致的线上故障。回滚测试的难度在于:需要覆盖的可能性非常多,一个发布可能在任何一个点上回滚。回滚可能还会引发兼容性问题:新代码生成的数据,在新代码被回滚后,老代码是否还能正确的处理这些数据。

    十三 兼容性测试

    代码和数据的兼容性问题有很多形式。例如,如何确保新代码能够正确的处理所有的老数据?有时候,老数据是几个月前的老代码产生的,例如,一个正向支付单据可能会到几个月以后才发生退款退票。有时候,老数据可能就是几分钟前产生的:用户的一个操作,背后的流程执行到中间的时候代码被升级了。验证这些场景下的兼容性的难度在于:需要验证的可能性太多了。今天的退款请求对应的正向单据,可能是过去很多个版本的代码产生的。一个业务流程执行到中间具体什么地方代码被升级了,可能性也非常多。

    异常测试、并发测试、回滚测试、兼容性测试,这些问题的一个共同点是:我们知道这些问题是可能存在的,但要测的话,需要测的可能性又太多。

    十四 Mock

    测试的有效性也依赖于mock的正确性。既然是mock,它和被mock的服务(包括内部的、二方的和三方的)的行为就多多少少会有差异。这种差异就有可能导致bug被漏过。前人也为此想出了“流量比对”等办法。我曾经有另一个想法:“一鸭三吃”。也就是说,通过bundle和compiler instruction等方法,让同一套源代码支持三种不同的编译构建模式:

    • 正常模式:这就是和今天的编译构建是一样的,产出的构建物是拿去生产环境跑的。
    • Mock模式:这个模式编译出来的就是该服务的一个mock,但由于是同一套代码编译出来的,最大可能的保留了原来的业务逻辑,做到最大限度的仿真。而且由于是同一套代码编译出来的,后期也不会有“脱钩”的担心,应用代码里的业务逻辑变化都能及时反映在mock里,大大减少mock的人肉维护工作量。
    • 压测模式:这个模式编译出来的也是一个mock,但这个mock是用来给(上游)做性能测试用的。过去在线下的性能压测中经常遇到的情况是:我们想要压的系统还没到瓶颈,这个系统的下游系统(往往是一个测试环境)反而先到瓶颈了。压测模式编译出来的这个mock牺牲了一部分的业务逻辑仿真,但能确保这个mock本身性能非常好,不会成为性能瓶颈(但对lantency仍然是仿真的)。

    这个“一鸭三吃”的想法so far还停留在想法层面,我还一直没有机会实践一下。

    十五 静态代码分析

    有一些类型的问题,要用通常意义上的软件测试来发现,难度和成本很高,但反而是通过静态代码分析来发现反而比较容易。例如,ThreadLocal变量忘记清除,会导致内存溢出、会导致关键信息在不同的不同的上游请求之间串错。另一个例子是NullPointerException。一种做法是通过fuzz testing、异常测试等手段来暴露代码里的NPE缺陷,以及可以在执行测试回归的时候观察log里面的NPE。但我们也可以通过静态代码分析,更早的就发现代码里面可能存在的NPE。有一些并发问题也可以通过静态代码分析来早期准确发现。总之,我们希望尽可能多的通过静态代码分析来防住问题。

    十六 形式化验证(Formal Verificaition)

    除了在协议、芯片、关键算法等上面的运用以外,形式化方法在更偏业务的层面是否有运用的价值和可能?

    十七 防错设计(Mistake Proof)

    严格来说,防错设计并不是software testing范畴内的。但做测试做久了就发现,有很多bug、很多故障,如果设计的更好一点,就压根不会发生(因此也就谈不上需要测试了)。去年我总结了一下支付系统的防错设计,后面希望能看到在各类软件系统形态下的防错设计原则都能总结出来,另外,最好还能有一些技术化的手段来帮助更好的落地这些防错设计原则,这个难度可能比总结设计原则的难度更高。

    十八 可测性(Testability)

    虽然目前大部分开发和QA同学都知道“可测性”这么件事情,但对可测性把握的还不够体系化,很多同学觉得可测性就是开接口、加test hook。或者,还没有很好的理解可测性这个东西落到自己这个领域(例如支付系统、公有云、ERP)意味着什么。在需求和系统设计分析阶段还不能做到很有效很有体系的从可测性角度提出要求,往往要求比较滞后。我希望可测性设计可以总结出一系列像程序设计的DRY、KISS、Composition Over Inheritance、Single Responsibility,Rule of Three等设计原则,总结出一系列的反模式,甚至出现像《设计模式》那样的一本专门的著作。

    以上就是我会加到Hard Problems in Test列表的问题,也是我已经或打算投入精力解决的问题。

    注:

    [1] 我工作中还有一些其他的测试难题,那些问题在这里没有列出来,因为那些问题和特定的业务场景或者技术栈的相关度比较高。还有一些测试领域的挑战,难度也很高,例如,回归测试达到99%以上通过率、主干开发以及做到通过代码门禁的code change就是可以进入发布的,这些也非常有难度,但难度主要是是偏工程的而不是软件测试技术本身。
    [2] 测试充分度的度量和提升是两个问题。有一种观点认为,测试充分的度量和提升其实是一件事情,用同样的算法分析数据可以进行度量,也能用同样的算法来基于数据进行测试充分度的提升。我不同意这个观点。度量和提升未必是同一个算法。这样的例子非常多了:测试有效性的度量和提升、运维稳定性的度量和提升,等等。即便度量和提升可以用同一种算法,我也希望可以尽量再找一些其他方法,尽量不要用同一种算法又做度量又做提升,因为这样容易“闭环”和产生盲区和。
    [3] 当然,这句话今天可能不再是那样了,但那是十几年前,那时候的在线广告和大数据还没到今天这个水平。
    [4] 具体形式上,这个数据银行无需是一个平台。它不一定是一个服务,它也不一定需要有UI。它可以就是一个jar包,它可以就是在测试执行时launch的一个单独的进程。

    ]]>
    Excelize 2.3.0 发布,Go 语言 Excel 文档基础库-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    github.com/360EntSecGroup-Skylar/excelize

    Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office Open XML 标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。入选 2018 开源中国码云 Gitee 最有价值开源项目 GVP,目前已成为 Go 语言最受欢迎的 Excel 文档基础库。

    开源代码

    GitHub: github.com/xuri/excelize
    Gitee: gitee.com/xurime/excelize
    中文文档: xuri.me/excelize/zh-hans

    2020年8月10日,社区正式发布了 2.3.0 版本,该版本包含了多项新增功能、错误修复和兼容性提升优化。下面是有关该版本更新内容的摘要,完整的更改列表可查看 changelog

    Release Notes

    此版本中最显著的变化包括:

    新增功能

    • 支持并发设置单元格的值,相关 issue #670
    • 新增 API: SetSheetFormatPrGetSheetFormatPr,支持设置工作表格式属性,相关 issue #635
    • 新增 API: GetColsCols 列迭代器
    • AddChart 添加图表 API 支持指定 Y 轴对数刻度,相关 issue #661
    • AddPicture 添加图片 API 支持插入图片自适应单元格
    • 增加对行、列和工作簿名称的长度上限的检查
    • 公式计算引擎支持自定义名称,相关 issue #665
    • API CalcCellValue 更新:新增 12 项函数, COUNTA, ISBLANK, ISERR, ISERROR, ISEVEN, ISNA, ISNONTEXT, ISODD, ISNUMBER, MEDIAN, NASUMIF

    兼容性提升

    • 兼容不同大小写的文档内部组件路径
    • 字符型单元格的值存储于共享字符表中,降低生成文档体积
    • 支持工作表中无 r 属性的 row 标签,以修复部分情况下读取工作表内容为空的问题
    • 支持多命名空间的 XML 标签,以兼容金山 WPS 等电子表格应用程序,解决 issue #651
    • 自动筛选器兼容 Office 2007 - 2010 版本的电子表格应用程序,解决 issue #637

    问题修复

    • 修复因工作簿内产生了重复的筛选数据库而导致的文档损坏问题
    • 避免添加样式时生成重复的样式定义
    • 修复设置富文本中包含的特殊字符丢失问题
    • 修复使用 Office 应用程序打开添加批注的文档,保存后再次打开批注形状不为矩形的问题,解决 #672
    • 避免部分情况下开启编译内联优化参数时潜在的运行时 panic 问题,解决 issue #677 和 #679
    • 修正获取百分比单元格值的数值精度问题

    其他

    • 修复特定情况下打开无效工作表时出现的异常 panic 问题
    • 完善单元测试中的错误处理
    • 包含简体中文、英语、法语、俄语、日语和韩语的多国语言文档网站更新
    • 技术交流群

    DingTalk Group ID: 30047129
    QQ Group ID: 207895940

    Excel 技术交流群

    ]]>
    Mysql的事务实现原理「收藏」-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Mysql的事务实现原理「收藏」
    1 开篇

    相信大家都用过事务以及了解他的特点,如原子性(Atomicity),一致性(Consistency),隔离型(Isolation)以及持久性(Durability)等。今天想跟大家一起研究下事务内部到底是怎么实现的,在讲解前我想先抛出个问题:

    事务想要做到什么效果?

    按我理解,无非是要做到可靠性以及并发处理。

    可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash的时候需要保障数据的操作前后的一致,想要做到这个,我需要知道我修改之前和修改之后的状态,所以就有了undo log和redo log。

    并发处理:也就是说当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。

    下面我首先讲实现事务功能的三个技术,分别是日志文件(redo log 和 undo log),锁技术以及MVCC,然后再讲事务的实现原理,包括原子性是怎么实现的,隔离型是怎么实现的等等。最后在做一个总结,希望大家能够耐心看完

    redo log与undo log介绍
    mysql锁技术以及MVCC基础
    事务的实现原理
    总结
    2 redo log 与 undo log介绍

    1. redo log

    什么是redo log ?

    redo log叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。假设有个表叫做tb1(id,username) 现在要插入数据(3,ceshi)

    Mysql的事务实现原理「收藏」
    start transaction;select balance from bank where name="zhangsan";// 生成 重做日志 balance=600update bank set balance = balance - 400; // 生成 重做日志 amount=400update finance set amount = amount + 400;commit;
    Mysql的事务实现原理「收藏」
    redo log 有什么作用?

    mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。

    那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操作。这样会导致丢部分已提交事务的修改信息!

    所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。

    总结:

    redo log是用来恢复数据的 用于保障,已提交事务的持久化特性

    2.undo log

    什么是 undo log ?

    undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

    还用上面那两张表

    Mysql的事务实现原理「收藏」
    每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。

    undo log 有什么作用?

    undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

    总结:

    undo log是用来回滚数据的用于保障 未提交事务的原子性

    3 mysql锁技术以及MVCC基础

    1. mysql锁技术

    当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。

    读写锁

    解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:

    共享锁(shared lock),又叫做"读锁"

    读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。

    排他锁(exclusive lock),又叫做"写锁"

    写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。

    Mysql的事务实现原理「收藏」
    总结:

    通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行

    事务的隔离性就是根据读写锁来实现的!!!这个后面再说。

    1. MVCC基础

    MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。

    InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号

    以上片段摘自《高性能Mysql》这本书对MVCC的定义。他的主要实现思想是通过数据多版本来做到读写分离。从而实现不加锁读进而做到读写并行。

    MVCC在mysql中的实现依赖的是undo log与read view

    undo log :undo log 中记录某行数据的多个版本的数据。
    read view :用来判断当前版本数据的可见性
    Mysql的事务实现原理「收藏」
    4 事务的实现

    前面讲的重做日志,回滚日志以及锁技术就是实现事务的基础。

    事务的原子性是通过undolog来实现的事务的持久性性是通过redolog来实现的事务的隔离性是通过(读写锁+MVCC)来实现的而事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的!!!
    原子性,持久性,隔离性折腾半天的目的也是为了保障数据的一致性!

    总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。

    1.原子性的实现

    什么是原子性:

    一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。

    上面这段话取自《高性能MySQL》这本书对原子性的定义,原子性可以概括为就是要实现要么全部失败,要么全部成功。

    以上概念相信大家伙儿都了解,那么数据库是怎么实现的呢?就是通过回滚操作。所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时怎么发挥作用的

    1.1 undo log 的生成

    假设有两个表 bank和finance,表中原始数据如图所示,当进行插入,删除以及更新操作时生成的undo log如下面图所示:

    Mysql的事务实现原理「收藏」
    Mysql的事务实现原理「收藏」
    从上图可以了解到数据的变更都伴随着回滚日志的产生:

    (1) 产生了被修改前数据(zhangsan,1000) 的回滚日志

    (2) 产生了被修改前数据(zhangsan,0) 的回滚日志

    根据上面流程可以得出如下结论:

    1.每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上

    2.所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

    思考:为什么先写日志后写数据库? ---稍后做解释

    1.2 根据undo log 进行回滚

    为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚

    Mysql的事务实现原理「收藏」
    回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:

    (1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句

    (2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句

    (3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句

    2.持久性的实现

    事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

    先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:

    读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;

    写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

    上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!

    因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。

    于是 redo log就派上用场了。下面看下redo log是什么时候产生的

    Mysql的事务实现原理「收藏」
    既然redo log也需要存储,也涉及磁盘IO为啥还用它?

    (1)redo log 的存储是顺序存储,而缓存同步是随机操作。

    (2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

    3.隔离性实现

    隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

    级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。

    Mysql 隔离级别有以下四种(级别由低到高):

    READUNCOMMITED(未提交读)READCOMMITED(提交读)REPEATABLEREAD(可重复读)SERIALIZABLE (可重复读)
    只要彻底理解了隔离级别以及他的实现原理就相当于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。

    那么隔离性是要做到什么呢? 隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行

    说明一点,写请求不仅仅是指insert操作,又包括update操作。

    Mysql的事务实现原理「收藏」
    总之,从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡。

    可靠性性高的,并发性能低(比如Serializable)可靠性低的,并发性能高(比如 Read Uncommited)
    READ UNCOMMITTED

    在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。

    因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。

    换句话说,读的操作不能排斥写请求。

    Mysql的事务实现原理「收藏」
    优点:读写并行,性能高

    缺点:造成脏读

    READ COMMITTED

    一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。

    InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。

    但是该级别会产生不可重读以及幻读问题。

    什么是不可重读?

    在一个事务内多次读取的结果不一样。

    为什么会产生不可重复读?

    这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

    在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

    REPEATABLE READ(Mysql默认隔离级别)

    在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

    采用读写锁实现:

    Mysql的事务实现原理「收藏」
    为什么能可重复度?只要没释放读锁,在次读的时候还是可以读到第一次读的数据。

    优点:实现起来简单

    缺点:无法做到读写并行

    采用MVCC实现:

    Mysql的事务实现原理「收藏」
    为什么能可重复度?因为多次读取只生成一个版本,读到的自然是相同数据。

    优点:读写并行

    缺点:实现的复杂度高

    但是在该隔离级别下仍会存在幻读的问题,关于幻读的解决我打算另开一篇来介绍。

    SERIALIZABLE

    该隔离级别理解起来最简单,实现也最单。在隔离级别下除了不会造成数据不一致问题,没其他优点。

    Mysql的事务实现原理「收藏」
    Mysql的事务实现原理「收藏」
    --摘自《高性能Mysql》

    4.一致性的实现

    数据库总是从一个一致性的状态转移到另一个一致性的状态。

    下面举个例子:zhangsan 从银行卡转400到理财账户

    start transaction;select balance from bank where name="zhangsan";// 生成 重做日志 balance=600update bank set balance = balance - 400; // 生成 重做日志 amount=400update finance set amount = amount + 400;commit;
    1.假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态。

    2.又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。

    3.假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。

    总结

    本出发点是想讲一下Mysql的事务的实现原理。

    实现事务采取了哪些技术以及思想?

    原子性:使用 undo log ,从而达到回滚
    持久性:使用 redo log,从而达到故障后恢复
    隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
    一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
    有一些比较火热的学习资料以及技术整理,我都会放到这里
    公众号:Java架构师联盟
    git地址:https://gitee.com/biwangsheng/mxq

    ]]>
    记录一次大规模数据库迁移(java)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 @[toc]
    在这里插入图片描述

    为什么要数据迁移

    • 1.系统重构 (代码跟不上现在的用户量)
    • 2.数据库设计不合理,与sql 垃圾,导致数据库qps大大的降低,从而导致数据库挂掉
    • 3.业务不断增长,现有资源不够用,战略的转移

    当这些问题出现的时候,我们会选择更好的系统架构 ,与解决方案,但是有个不可避免的问题 (数据迁移)当原有系统存在 几十万,几百万用户数据的时候,我们就要考虑,这些用户数据这样才能完美的在新系统 或者新数据库上应用

    真实案例:

    • 数据库建立的不合理,数据格式错乱,sql效率低下,导致索引失效,用户访问量达 C端项目,导致数据库服务器和 应用服务器天天挂,
    • 所以决定系统重构,数据库重新设计
    • 在开发完成,项目上线前一个阶段,就要设计要数据迁移

    解决方案

    1. 导入固定的数据

      ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200802151031610.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxOTc3ODM4,size_16,color_FFFFFF,t_70)
      
    2. 导入用户数据
      在这里插入图片描述
    3. 做好之前数据库以及现在测试数据库的备份,以及快照
    4. 少于 5w 以下的数据可以使用 并且 数据格式一样的数据可以使用
    5. into (select * from aa) 大于5w 以上的数据,就不能使用
    6. 大规模数据迁移方式,首先就要考虑多线程 方式,单线程方式可能需要哥好几十个小时

      • 如果跑代码脚本的话 最好考虑 @asyc 注解式多线程操作
      • 如果需要做关系,切记一定要加索引,当迁移百万级别的数据的时候,就会发现索引的威力是多么强大

    案例

    @PostMapping("move")
        public ResultBody moveUnderOrderrelationship() {
            for (int i = 1; i <50 ; i++) {
                int pageNum = (i-1) * 10000;
                undersService.moveUnderOrderrelationship(pageNum,10000);
            }
            return ResultBody.success();
        }
    
        @Async
        public void moveUnderOrderrelationship(int pageNum, int pageSize) {
                    ……
            }
            
    select * from *** limit #{pageNum},#{pageSize}
    ]]>
    【其他】8月5日轻量云服务器国际站香港及海外地域价格调整通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【轻量应用服务器】【规格/价格调整通知】

    为了更贴近客户实际业务场景,提升产品服务质量,轻量应用服务器(SAS)计划于8月5日对国际站香港地域和海外地域进行套餐规格和价格的调整。

    8.5日前持有如上地域套餐的客户,续费和升级仍可按照原套餐规格及价格执行,8月5日起新购套餐需按照调整后规格及价格执行。

    ]]>
    【其他】8月12日视频点播、直播CDN亚太1、2、3区降价通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【视频点播】【视频直播】【降价通知】

    降价时间:2020年8月12日  10:00

    降价说明:为了回馈客户并分享CDN规模和技术不断升级带来的红利。本次对视频点播、直播进行降价。

    覆盖区域:亚太1区-AP1、亚太2区-AP2和亚太3区-AP3

    覆盖的计费方式:

    1)按流量和峰值带宽的计费方式

    2)点播流量包,100GB、500GB、1TB、5TB、10TB、50TB规格

    其他区域暂无调整。具体调整如下:


    按流量计费

    按峰值带宽计费


    点播流量包



    ]]>
    ACK 1.9 版本K8S升级指南 Fri, 20 Jun 2025 02:20:33 +0800 ACK 1.9已经超出技术支持期限,目前升级管控已经无法适配1.9升级,本文主要指导用户修改1.9的相关配置,使其可以进行升级。

    目前主要阻碍是1.9集群内置的DNS服务组件为kube-dns,我们需要将其替换为CoreDNS,就可以使用容器服务提供的升级功能进行升级了。

    首先,我们需要在集群中部署CoreDNS服务。注意需要将image修改为您的集群所在的regionid,如你的集群在杭州则将

    image: registry-vpc.YOUR-REGIONID.aliyuncs.com/acs/coredns:1.0.6

    修改为

    image: registry-vpc.cn-hangzhou.aliyuncs.com/acs/coredns:1.0.6

    完整yaml文件如下:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: coredns
      namespace: kube-system
      labels:
        k8s-app: kube-dns
    spec:
      replicas: 2
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 1
      selector:
        matchLabels:
          k8s-app: kube-dns
      template:
        metadata:
          labels:
            k8s-app: kube-dns
        spec:
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                      - key: k8s-app
                        operator: In
                        values: ["kube-dns"]
                  topologyKey: kubernetes.io/hostname
          serviceAccountName: coredns
          tolerations:
          - operator: Exists
          nodeSelector:
            beta.kubernetes.io/os: linux
          containers:
          - name: coredns
            image: registry-vpc.YOUR-REGIONID.aliyuncs.com/acs/coredns:1.0.6
            imagePullPolicy: IfNotPresent
            resources:
              limits:
                memory: 170Mi
              requests:
                cpu: 100m
                memory: 70Mi
            args: [ "-conf", "/etc/coredns/Corefile" ]
            volumeMounts:
            - name: config-volume
              mountPath: /etc/coredns
              readOnly: true
            ports:
            - containerPort: 53
              name: dns
              protocol: UDP
            - containerPort: 53
              name: dns-tcp
              protocol: TCP
            - containerPort: 9153
              name: metrics
              protocol: TCP
            livenessProbe:
              httpGet:
                path: /health
                port: 8080
                scheme: HTTP
              initialDelaySeconds: 60
              timeoutSeconds: 5
              successThreshold: 1
              failureThreshold: 5
            securityContext:
              allowPrivilegeEscalation: false
              capabilities:
                add:
                - NET_BIND_SERVICE
                drop:
                - all
              readOnlyRootFilesystem: true
          dnsPolicy: Default
          volumes:
            - name: config-volume
              configMap:
                name: coredns
                items:
                - key: Corefile
                  path: Corefile
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
    data:
      Corefile: |
        .:53 {
            errors
            health
            kubernetes cluster.local in-addr.arpa ip6.arpa {
               pods insecure
               upstream
               fallthrough in-addr.arpa ip6.arpa
            }
            prometheus :9153
            proxy . /etc/resolv.conf
            cache 30
            loadbalance
        }
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: system:coredns
    rules:
    - apiGroups:
      - ""
      resources:
      - endpoints
      - services
      - pods
      - namespaces
      verbs:
      - list
      - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: system:coredns
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: system:coredns
    subjects:
    - kind: ServiceAccount
      name: coredns
      namespace: kube-system
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: coredns
      namespace: kube-system

    使用该yaml创建CoreDNS服务后,需要观察其是否可以正常运行。

    确定CoreDNS正常运行之后,可以逐步减少集群中kube-dns的副本数(spec. replicas)。

    最后在确保业务正常运行的前提下,我们可以删掉集群中的kube-dns。

    完成上述操作步骤之后,您就可以使用容器服务的集群升级功能进行集群升级了。

    如果集群升级之后又出现了kube-dns,您可以将其删除。

    ]]>
    KubeCon 速递 | 云原生操作系统进化,详解阿里云ACK Pro、ASM、ACR EE、ACK@Edge等四款企业级容器新品 Fri, 20 Jun 2025 02:20:33 +0800 KubeCon 2020中国站,阿里云容器服务负责人易立会在《云原生,数字经济技术创新基石》的演讲中,分享阿里云原生如何助力数字技术抗‘疫’,阐述阿里云对云原生操作系统的思考,同时详解阿里云ACK Pro、ASM、ACR EE、ACK@Edge四款企业级容器新品。

    容器服务ACK Pro,为企业级大规模生产环境提供增强的可靠性安全性,以及与可赔付标准SLA,现已开启公测。同时还有三款产品商业化:服务网格ASM,统一精准管控容器化微服务应用流量;容器镜像服务企业版ACR EE,公共云首个独享实例形态的容器镜像仓库产品,是支撑阿里巴巴经济体的双十一同款利器,具备如下能力:多维度安全保障、全球镜像分发加速、DevSecOps 交付提效特点,保障企业客户云原生制品的安全托管及高效分发;边缘容器ACK@Edge采用非侵入方式增强,提供边缘自治、边缘单元、边缘流量管理、原生运维API支持等能力,以原生方式支持边缘计算场景下的应用统一生命周期管理和统一资源调度。

    疫情期间,争分夺秒的云原生

    云计算是数字时代新基建,而2020疫情也为数字化生活按下了快进键。“上班用钉钉,上学云课堂,出门健康码,订菜送到家”成为了日常生活一部分,这背后是一系列云计算、云原生技术支撑的业务创新。

    2小时内支撑了钉钉业务1万台云主机的扩容需求,基于阿里云服务器和容器化的应用部署方案,钉钉应用发布扩容效率大大提升,顺利扛住有史以来最大的流量洪峰,为全国用户提供线上工作的流畅体验。

    停课不停学,希沃课堂整体业务性能提升30%、运维成本降低50%,洋葱学院系统资源利用率提升60%。
    健康码基于云原生大数据平台具备弹性、韧性和安全的特点,平稳支撑每日亿次调用。

    盒马通过阿里云边缘容器服务ACK@Edge,快速构建人、货、场数字化全链路融合,云、边、端一体化协同的天眼AI系统。结合了云原生技术体系良好的资源调度和应用管理能力,与边缘计算就近访问,实时处理的优势,轻松实现全方位的『降本提效』,门店计算资源成本节省50%,新店开服效率提升70%。

    image.png

    云原生操作系统的诞生与进化

    容器技术的发展揭开了云原生计算的序幕,在易立看来, Kubernetes为基础的云原生计算也已经成为新的操作系统,云原生操作系统的雏形被越来越多的行业和企业采纳并因此受益:容器应用化、容器编排系统和Istio服务网格等技术依次解耦了应用与运行环境、资源编排调度与底层基础设施、服务实现与服务治理等传统架构的依赖关系。

    image.png

    阿里云为用户提供了怎样的云原生操作系统?这个云原生操作系统又有哪些突出特点呢?

    首先基础设施层是强大的IaaS资源,基于第三代神龙架构的计算资源可以更弹性的扩展,以更加优化的成本提供更高的性能;云原生的分布式文件系统,为容器持久化数据而生;云原生网络加速应用交付能力,提供应用型负载均衡与容器网络基础设施。

    image.png

    其次在容器编排层,阿里云容器服务自2015年上线来,伴随数千家企业客户,共同实践过各行各业大量生产级场景。越来越多的客户以云原生的方式架构其大部分甚至全量应用,随着业务深入发展,为了满足大中型企业对可靠性、安全性的强烈需求,阿里云推出新品可供赔付SLA的容器服务企业版ACK Pro。

    容器服务企业版ACK Pro横空出世:全面安全、高性能,支持新一代神龙架构,SLA可赔付

    容器服务企业版ACK Pro,是在原容器服务ACK托管版集群的基础上发展而来,其继承了原托管版集群的所有优势,例如Master节点托管和高可用等。同时,相比原托管版进一步提升了集群的可靠性、安全性和调度性能,并且支持赔付标准的SLA,高达99.95%,单集群可支持5000节点。ACK Pro非常适合生产环境下有着大规模业务、对稳定性和安全性有高要求的企业客户。

    ACK Pro支持神龙架构,凭借软硬一体的优化设计,可为企业应用提供卓越的性能;支持无损Terway容器网络,相比路由网络延迟下降 30%;为企业提供全面安全防护,支持阿里云安全沙箱容器,满足企业客户对应用的安全、隔离需求,性能相比开源提升30%。此外,ACK Pro提供了对异构算力和工作负载优化的高效调度,支持智能CPU调度优化,在保障SLA和密度的前提下,Web应用QPS提升30%;支持GPU算力共享, AI模型预测成本节省50%以上。

    阿里云视频云已在全球十多个区域使用容器服务作为服务基础,有效管理全球万级节点的资源,其中ACK Pro切实保障基础设施层大规模计算资源的运维效率和高稳定性,使视频云团队可以将更多时间精力聚焦在视频领域从而为客户提供更多价值。

    近日, 阿里云容器服务并成为国内首批通过可信云容器安全先进性认证的企业级容器平台,以49个满分项目荣获最高级别先进级认证,特别是在最小化攻击面,二进制镜像验签名,密文的BYOK加密等能力上国内领先,达到国际先进水平。

    容器服务ACK Pro现已正式开启公测,非常适用于互联网、大数据计算、金融政府及跨海业务企业等,欢迎各位感兴趣的客户在官网上申请试用。

    业内首个全托管Istio兼容服务网格ASM,多种异构应用服务统一治理

    在服务网格技术诞生前,应用服务治理的逻辑实现通常都是以代码库的方式嵌入在应用程序中,随着应用复杂度的增长和团队规模的扩大,代码库变更和维护会变地越来越复杂。服务网格通过Sidecar代理功能,将服务治理能力与应用程序本身解耦,将服务治理能力标准化、统一化,且更好地支持多种编程语言和技术框架的应用服务。

    作为业内首个全托管Istio兼容的服务网格产品ASM,已经正式商业化发布,用户可以在多个地域部署使用。阿里云一开始从架构上就保持了与社区、业界趋势的领先性,控制平面的组件托管在阿里云侧,与数据面侧的用户集群独立。ASM在托管的控制面侧提供了用于支撑精细化的流量管理和安全管理的组件能力。通过托管模式,解耦了Istio组件与所管理的K8s集群的生命周期管理,使得架构更加灵活,提升了系统的可伸缩性。

    商米科技使用服务网格ASM之后,充分享受了Service Mesh带来的好处,解决了GRPC-HTTP2.0多路复用引起的负载不均衡的问题,得以分离控制平面和数据平面,受益于ASM的可视化方式对控制平面进行管理,对规则配置一目了然。同时还无缝结合链路监控(Tracing Analysis)解决服务化体系下调用链排查追踪问题。

    作为多种类型计算服务统一管理的基础设施, ASM提供了统一的流量管理能力、统一的服务安全能力、统一的服务可观测性能力、以及统一的数据面可扩展能力, 并提出了相应的实践之成熟度模型。针对用户不同的场景, 逐步采用, 分别为一键启用、可观测提升、安全加固、多种基础设施的支持以及多集群混合管理。

    全球镜像同步加速,ACR EE 保障云原生制品的安全托管及高效分发

    容器镜像服务企业版 ACR EE 面向安全及性能需求高,业务多地域大规模部署的企业级客户,提供了公共云首个独享实例模式的企业版服务。

    在制品托管部分,ACR EE 除了支持多架构容器镜像,还支持多版本 Helm Chart 等符合 OCI 规范制品托管。在分发及安全治理部分,ACR EE 加强了全球多地域分发和大规模分发支持,提供网络访问控制、安全扫描、安全加签、安全审计等多维度安全保障,助力企业从 DevOps 到 DevSecOps 的升级。目前已有数百家企业线上环境大规模使用,保障企业客户云原生应用制品的安全托管及高效分发。

    某国际零售巨头是全球多地域业务形态,存在多地研发协作及多地域部署的场景。采用 ACR EE 之后,客户只需配置实例同步规则,在业务迭代更新容器镜像后,可实现分钟级自动同步至全球指定地域,自动触发 ACK 集群业务部署。完美应对跨海网络链路不稳定、分发不安全的问题,极大提高业务研发迭代效率和部署稳定性,保障企业业务的全球化部署。

    除了业务镜像全球自动化部署,也有很多企业通过ACR EE的云原生应用交付链功能,通过全链路可追踪、可观测、可自主配置等实践容器化DevSecOps。

    业界首创“云边一体化”理念 ,边缘容器服务ACK@Edge正式商业化

    与此同时,阿里云深度挖掘了“边缘计算+云原生落地实施”诉求,在去年KubeCon上,重磅发布了边缘容器(ACK@Edge),时隔一年,宣布ACK@Edge 正式商用。此外,ACK@Edge 也将其核心能力开源,并向社区贡献完整的云原生边缘计算项目——OpenYurt。

    在过去短短一年的时间里,该款产品已经应用于音视频直播、云游戏、工业互联网、交通物流、城市大脑等应用场景中,并服务于盒马、优酷、阿里视频云和众多互联网、新零售企业。

    YY使用ACK@Edge之后,可以API统一管理、统一运维边缘容器集群和中心容器集群,边缘算力快速接入并实现边缘节点自治,同时也可以无缝接入Prometheus服务实现监控数据上报,总体上运维效率和资源使用率都得到显著改善。

    ACK@Edge适用于丰富的应用场景, 包括边缘智能、智慧楼宇、智慧工厂、音视频直播、在线教育、CDN。

    云原生技术不但可以最大化云的弹性,帮助企业实现降本提效; 而且还意味着更多创新的想象空间, 云原生将和AI, 边缘计算,机密计算等新技术相结合,为数字经济构建智能、互联、信任的创新基础设施。

    “ 新基石、新算力、新生态是容器产品发展策略 ” ,易立称,“云原生技术正成为释放云价值的最短路径,团队会帮助企业更好支撑混合云、云边一体的分布式云架构和全球化应用交付。基于云原生的软硬一体化技术创新,如神龙架构,含光芯片,GPU共享调度等,阿里云将加速业务智能化升级。同时我们还会通过开放技术生态和全球合作伙伴计划,让更多企业分享云时代技术红利。”

    ]]>
    在线教育行业,不可不知的阿里云中间件实战秘笈 Fri, 20 Jun 2025 02:20:33 +0800 “停课不停学”,疫情之下,几乎所有教育机构或平台都趁热打铁,上百项公益课程纷至沓来,令人眼花缭乱。据统计,从年初至今,13家在线教育相关公司的市值已经累计上涨近800亿元。线下教育停滞,巨额流量瞬间涌入线上。此次疫情对于在线教育的快速普及起到了很大的刺激作用,主要地区普及率将从目前的不到20%快速提升到接近100%,并将在线教育的学习方式推广到三四线城市。

    从在线教育长远的发展来讲,根据艾媒咨询的数据,2018年中国在线教育用户规模超过2亿人,而到2020年,有望达到3亿人。根据艾瑞咨询的数据,2018年中国在线教育市场规模2518亿元,预计在2022年市场规模将超过5000亿元。

    在线教育行业基于用户在线、产品在线、服务在线的需求,需要突破时间和空间的限制,打破由于地域限制等原因造成的教育资源不平等分配,从而降低学习门槛,使教育资源共享化。同时,在线教育行业还需要通过技术手段和能力,逐步将内容供应、平台、分发推广为一体的生态能力构建起来。从商业逻辑出发,其核心环节包括两个节点:流量的引入、流量的变现。

    在流量快速涌入的背景下,对在线教育机构及第三方服务提供商既是机遇,亦是挑战。挑战主要在:
    1、企业面对越来越多的挑战:师资的招聘(尤其是疫情期间)、用户爆发增长下的服务维持、系统的稳定性能力。
    2、如何应对不确定性:“黑天鹅”现象频发,如何通过IT技术应对突发流量、抖增的业务量以及不确定的系统容量需求,从而让企业夺得先机。

    从流量引入以及构建在线教育平台的核心竞争力来讲,挑战在于:
    1、 新的产品、内容以及新的业务快速落地的能力:互联网时代唯快不破,新的业务如何快速试点不断迭代,从而吸引更多的用户,是技术赋能业务时需要快速解决的问题。
    2、 新业务上线以后,如何能引爆整个市场,服务更多的用户。
    3、 当服务于一定数量级用户后,如何精准和个性化地服务每一个客户。
    网络协同和数据智能是未来商业的基本特征。在线教育行业需要通过提升运营效率、业务创新能力和精准服务能力,进一步实现业务在线、用户在线和服务在线。而这些商业能力的实现依赖于IT基础设施云化、技术互联网化以及应用的数据化和智能化。
    云原生基于开放的技术标准、理念和实践,正成为云和企业交互的新界面。阿里云中间件作为云原生的核心支点,其产品和最佳实践正在加速在线教育IT基础设施云化和技术互联网化。当前,历经阿里巴巴各种业务打磨和验证的中间件,正以开放稳定的绝对优势,在阿里云在线教育等各个行业中广泛使用。

    此处通过一篇案例进行详细剖析:
    Timing App的Serverless实践案例
    1、背景和挑战:
    作为广受好评的学习应用,Timing App 专注于帮助社区用户提升学习凝聚力,达成学习目标。目前已有超过 700 万人通过 Timing 进行高效学习。与传统在线学习应用不同,Timing app 提供了 Timing 自习室、图书馆学习、 视频打卡、学习日记、契约群、学习服务等多类具有社交性质的在线教育服务,帮助用户找到自己的学习节奏,找到 坚持学习的一万种理由。Timing 业务本身具有潮汐特性,用户访问主要集中在晚间和节假日。受疫情影响,春节期 间峰值流量暴增 4 倍,公司面临较大的运维成本压力。在用户、流量爆发式增长背景下,Timing App 不得不直面以 下四大痛点:
    (1)系统稳定性差。原有 PHP 单应用架构系统无法做到线性快速扩容,在业务高峰时段,系统问题频繁发生,严 重影响用户体验。
    (2)产品迭代缓慢。随着业务的高速发展,原有单体架构对于产品的迭代力不从心,没法快速响应研发需求。
    (3)资源使用浪费。由于业务具有非常强的流量潮夕特征,需要按照业务高峰阶段进行资源保有配置,造成资源 的浪费。
    (4)技术成本昂贵。以前的团队除了技术负责人及少数团队新成员外,基本缺乏微服务架构实战经验。想要实现 微服务改造,急需能够快速上手的平台支撑,需要最大限度降低底层 IaaS, 容器以及常用微服务套件的学习 成本。

    2、云原生解决方案:
    阿里云应用引擎 Serverless(SAE),基于 Serverless 架构,屏蔽了底层 IaaS 运维和 K8s 细节,区别 于 FaaS 形态的 Serverless 产品,用户无需修改编程模型,零代码改造就能直接使用。同时,完美结合 Spring Cloud/Duboo 等微服务架构,提供应用发布、管理和服务治理等应用全生命周期的服务,完美贴合 Timing 的技术 需求:极限弹性伸缩,应用生命周期灵活管理,完美支持主流微服务架构。
    下图是方案架构示意图。
    image.png

    3、方案的关键优势:
    (1)利用弹性伸缩,应对不确定突发流量。提供秒级自动弹性 & 定时弹性能力,帮助应用轻松应对大促峰值流量, 保证 SLA 的同时也节省机器保有成本。多适用于互联网、游戏、在线教育行业。 应用环境随需灵活启停,节约成本。提供了一键启停开发测试环境的能力,即开即用,节省成本,方便运维。 适用于对成本敏感、云上有多套环境但部分环境闲置率较高的企业型客户(不限行业)。
    (2)中小企业快速构建云上微服务应用。帮助用户屏蔽底层 IaaS 购买和运维细节、底层 K8s 细节,低门槛部署 微服务应用。适用于初创型 / 上升期的公司(不限行业),业务增长很快,对增长有较高预期,但人员配置跟不上。
    (3)整体技术架构更为清晰,每个服务相互独立且职责明确。加之阿里云应用引擎 Serverless (SAE)加持, 让客户只关注在业务层,做好产品。

    【在线教育行业,中间件实战秘笈】

    十年中间件精华集锦,一览标杆案例、最佳实践合集!还有全年重磅优惠大放送!点击链接立即查看:
    https://www.aliyun.com/activity/daily/aioe

    【填问卷抽礼品】

    为了更好地给您提供定制化服务,诚邀您填写问卷,我们将随机抽取用户赠送淘宝纪念版公仔或指尖陀螺哦!
    【点击链接马上填问卷抽礼品】
    https://survey.aliyun.com/apps/zhiliao/YmW95Gk8bU

    image.png

    【加入客户交流钉钉群】

    欢迎扫码加入在线教育客户交流钉钉群,阿里巴巴众多专家将在群内定期分享行业最佳实践和前沿技术干货,扫码入群,与更多行业精英互动交流。

    test

    ]]>
    阿里云飞天AI加速器+Serverless容器,帮助图森未来提升资源利用率 Fri, 20 Jun 2025 02:20:33 +0800 hailuo_164850200_RF_线上全媒体用途.jpg

    图森未来(TuSimple)成立于2015年,是一家专注于L4级无人驾驶卡车技术研发与应用的人工智能企业,已经实现卡车在干线物流场景和半封闭枢纽场景下的无人干预驾驶。图森未来品牌旗下产品——图森未来 L4 级别无人驾驶卡车能够实现环境感知、定位导航、决策控制等自动驾驶核心功能,可应用于高速公路货运和港内集装箱码头运输及其相似场景。

    公司于2019年9月完成总额2.15亿美元D轮融资,资方:UPS、鼎晖资本、万都中国、累计融资超过3亿美元,最新估值超过12亿美元,是卡车无人驾驶头部企业,也是全球第一家无人驾驶卡车独角兽企业。

    图森未来的业务主要在美国和国内两地展开,在美国主要是高速公路干路货运场景,国内业务开始以连接枢纽场景的干线物流货运为主。

    在美国,图森未来已经向包括UPS、USPS、McLane、U.S. Xpress在内的18家客户提供无人驾驶物流服务。

    今年年初,图森未来的无人驾驶卡车还上了“钢铁侠”的扮演者小罗伯特·唐尼主持的科技聚集剧集《The Age of A.I.》(《AI时代》),在剧中不仅顺利完成无人驾驶的行驶任务,还与围追堵截的摄像车“斗智斗勇”,在摄像车各种找拍摄角度的情况下,自动自我调整,化险为夷,避让既礼貌又安全。

    国内方面,2018年 10月,图森未来拿到国内第一张针对无人驾驶卡车在公开道路测试的测试牌照。依托上海市政府的政策支持,以及临港片区真实场景的需求,图森未来的无人驾驶卡车已经在东海大桥上进行了长时间的测试,截至2020年7月,累计测试里程已超过5万公里,具备了在东海大桥上不需要接管的无人驾驶能力。

    在北京,图森未来携手北汽福田、首发集团等合作伙伴,实现了在京礼高速(延崇北京段)总长14公里(包括9.8公里连续特长隧道群路段)的三车无人驾驶队列跟弛测试,顺利完成中国首次高速公路全封闭环境下、基于C-V2X车路协同技术的队列跟驰测试工作。

    飞天AI加速器提升训练性能,加速模型迭代

    一台车自动驾驶卡车,两个星期会产生大约50TB的数据。图森未来目前有超过70台卡车在上路,意味着每天有大量的数据产生。为了让自动驾驶卡车更聪明,就需要不断地积累更多的真实数据集训练它目标检测与物体识别框架的能力。

    随着业务高速发展,迭代越来越快,图森未来的模型也越来越复杂。每次模型迭代,都需要短时间调度大规模的GPU资源来分布式地进行模型训练。

    然而,GPU服务器采购成本高,运维复杂,图森未来不得不投入越来越多的精力到运维工作中;更重要的是,图森未来发现,随着所用GPU数量增长,GPU的利用率却并不高。

    为什么会这样?理论上来说,GPU卡越多,整体算力越大,但是随着机器数的增加,不同机器的GPU之间的配合难度会越来越大,单张GPU卡的利用率反而会下降。所以增加了几十倍的卡的成本,但是性能却很难随之线性增长。

    阿里云的飞天AI加速器AIACC团队,针对图森未来的场景,在底层针对通讯、计算、时延和带宽等做了深度优化,将训练性能提升了将近60%,大大缩短了图森未来的模型优化时间,加速模型迭代,提高技术门槛。

    飞天AI加速器是AIACC业界首个统一加速,Tensorflow、MXNet、Caffe、PyTorch等主流深度学习框架的加速引擎,拿下斯坦福深度学习榜单Dawnbench图像识别四个世界第一。

    Serverless容器,提升仿真测试效率,缩短60%模型测试时间

    每次迭代的模型训练完成之后,图森未来需要对优化后的模型进行测试。如果每次都要上路测试,成本大、风险高、而且不能验证各种极端情况。

    幸亏,图森未来有个汽车仿真平台,模拟在各种环境下(例如:晴天、阴天、雨天、雾霾天、夜晚),模型的处理能力。

    这种测试任务依赖开发人员的开发节奏,具有突发、临时、短期的特征,并且需要的算力规模非常大,如果包年包月地购买海量算力,则很多时候都处于闲置状态,需求来了可能算力又不够用,仿真模拟任务需要排队完成,影响开发人员的开发效率和模型的迭代速度。

    由于图森未来的整体业务架构早已实现容器化,为这类临时高峰场景做好了敏捷的业务储备。通过阿里云ASK(Alibaba Cloud Serverless Kubernetes)容器服务,图森未来可以在需要测试的时候,在阿里云上秒级启动大规模的容器集群,即刻获取海量算力,缩短了60%的模型测试时间;完成测试之后迅速释放算力,避免资源浪费。

    阿里云ASK是Serverless免运维的K8s容器服务,底层使用阿里云ECI(Elastic Container Instance弹性容器实例)作为容器计算基础设施,提供高弹性、低成本、免运维的Serverless容器运行环境,免去用户对容器集群的运维和容量规划工作,大大节省了图森未来运维的工作量。

    另外,ASK的计费粒度精确到秒,非常适用于仿真计算这类突发的高并发短时任务;针对长期的训练任务,图森未来则使用包年包月的ACK(Alibaba Cloud Kubernetes)。通过适用于长短任务的ACK+ASK产品搭配,即提升了图森未来的资源利用率,又节省了成本。

    ]]>
    掌门教育微服务体系 Solar | 阿里巴巴 Nacos 企业级落地中篇 Fri, 20 Jun 2025 02:20:33 +0800 25.jpg
    联席作者:吴毅挺 任浩军 童子龙
    郑重鸣谢:Nacos - 彦林,Spring Cloud Alibaba - 小马哥、洛夜,Nacos 社区 - 张龙(pader)、春少(chuntaojun)

    掌门教育自 2014 年正式转型在线教育以来,秉承“让教育共享智能,让学习高效快乐”的宗旨和愿景,经历云计算、大数据、人工智能、 AR / VR / MR 以及现今最火的 5G ,一直坚持用科技赋能教育。掌门教育的业务近几年得到了快速发展,特别是今年的疫情,使在线教育成为了新的风口,也给掌门教育新的机遇。

    随着业务规模进一步扩大,流量进一步暴增,微服务数目进一步增长,使老的微服务体系所采用的注册中心 Eureka 不堪重负,同时 Spring Cloud 体系已经演进到第二代,第一代的 Eureka 注册中心已经不大适合现在的业务逻辑和规模,同时它目前被 Spring Cloud 官方置于维护模式,将不再向前发展。如何选择一个更为优秀和适用的注册中心,这个课题就摆在了掌门人的面前。经过对 Alibaba Nacos 、HashiCorp Consul等开源注册中心做了深入的调研和比较,最终选定 Alibaba Nacos 做微服务体系 Solar 中的新注册中心。

    背景故事

    两次 Eureka 引起业务服务大面积崩溃后,虽然通过升级硬件和优化配置参数的方式得以解决,Eureka 服务器目前运行平稳,但我们依旧担心此类事故在未来会再次发生,最终选择落地 Alibaba Nacos 作为掌门教育的新注册中心。

    Nacos 开发篇

    Nacos Eureka Sync 方案演进

    Sync 官方方案

    经过研究,我们采取了官方的 Nacos Eureka Sync 方案,在小范围试用了一下,效果良好,但一部署到 FAT 环境后,发现根本不行,一台同步服务器无法抗住将近 660 个服务(非实例数)的频繁心跳,同时该方案不具备高可用特点。

    Sync 高可用一致性 Hash + Zookeeper 方案

    既然一台不行,那么就多几台,但如何做高可用呢?

    我们率先想到的是一致性 Hash 方式。当一台或者几台同步服务器挂掉后,采用 Zookeeper 临时节点的 Watch 机制监听同步服务器挂掉情况,通知剩余同步服务器执行 reHash ,挂掉服务的工作由剩余的同步服务器来承担。通过一致性 Hash 实现被同步的业务服务列表的平均分配,基于对业务服务名的二进制转换作为 Hash 的 Key 实现一致性 Hash 的算法。我们自研了这套算法,发现平均分配的很不理想,第一时间怀疑是否算法有问题,于是找来 Kafka 自带的算法(见 Utils.murmur2 ),发现效果依旧不理想,原因还是业务服务名的本身分布就是不平均的,于是又回到自研算法上进行了优化,基本达到预期,下文会具体讲到。但说实话,直到现在依旧无法做到非常良好的绝对平均。

    Sync 高可用主备 + Zookeeper 方案

    这个方案是个小插曲,当一台同步服务器挂掉后,由它的“备”顶上,当然主备切换也是基于 Zookeeper 临时节点的 Watch 机制来实现的。后面讨论下来,主备方案,机器的成本很高,实现也不如一致性 Hash 优雅,最后没采用。

    Sync 高可用一致性 Hash + Etcd 方案

    折腾了这么几次后,发现同步业务服务列表是持久化在数据库,同步服务器挂掉后 reHash 通知机制是由 Zookeeper 来负责,两者能否可以合并到一个中间件上以降低成本?于是我们想到了 Etcd 方案,即通过它实现同步业务服务列表持久化 + 业务服务列表增减的通知 + 同步服务器挂掉后 reHash 通知。至此方案最终确定,即两个注册中心( Eureka 和 Nacos )的双向同步方案,通过第三个注册中心( Etcd )来做桥梁。

    Sync 业务服务名列表定时更新优化方案

    解决了一致性 Hash 的问题后,还有一个潜在风险,即官方方案每次定时同步业务服务的时候,都会去读取全量业务服务名列表,对于业务服务数较少的场景应该没问题,但对于我们这种场景下,这么频繁的全量去拉业务服务列表,会不会对 Nacos 服务器的性能有所冲击呢?接下去我们对此做了优化,取消全量定时读取业务服务名列表,通过 DevOps 的发布系统平台实施判断,如果是迁移过来的业务服务或者新上 Nacos 的业务服务,由发布平台统一调用 Nacos 接口来增加新的待同步业务服务 Job,当该业务服务全部迁移完毕后,在官方同步界面上删除该同步业务服务 Job 即可。

    Sync 服务器两次扩容

    方案实现后,上了 FAT 环境上后没发现问题(此环境,很多业务服务只部署一个实例),而在 PROD 环境上发现存在双向同步丢心跳的问题,原因是同步服务器来不及执行排队的心跳线程,导致 Nacos 服务器无法及时收到心跳而把业务服务踢下来。我们从 8 台 4C 8G 同步服务器扩容到 12 台,情况好了很多,但观察下来,还是存在一天内一些业务服务丢失心跳的情况,于是我们再次从 12 台 4C 8G 同步服务器扩容到 20 台,情况得到了大幅改善,但依旧存在某个同步服务器上个位数丢失心跳的情况,观察下来,那台同步服务器承受的某几个业务服务的实例数特别多的情况,我们在那台同步服务器调整了最大同步线程数,该问题得到了修复。我们将继续观察,如果该问题仍旧复现,不排除升级机器配置到 8C16G 来确保 PROD 环境的绝对安全。

    至此,经过 2 个月左右的努力付出,Eureka 和 Nacos 同步运行稳定, PROD 环境上同步将近 660 个服务(非实例数),情况良好。
    非常重要的提醒:一致性 Hash 的虚拟节点数,在所有的 Nacos Sync Server 上必须保持一致,否则会导致一部分业务服务同步的时候会被遗漏。

    Nacos Eureka Sync 落地实践

    Nacos Eureka Sync 目标原则

    注册中心迁移目标

    1、过程并非一蹴而就的,业务服务逐步迁移的过程要保证线上调用不受影响,例如, A 业务服务注册到 Eureka 上, B 业务服务迁移到 Nacos ,A 业务服务和 B 业务服务的互相调用必须正常。
    2、过程必须保证双注册中心都存在这两个业务服务,并且目标注册中心的业务服务实例必须与源注册中心的业务服务实例数目和状态保持实时严格一致。

    注册中心迁移原则

    1、一个业务服务只能往一个注册中心注册,不能同时双向注册。
    2、一个业务服务无论注册到 Eureka 或者 Nacos,最终结果都是等效的。
    3、一个业务服务在绝大多数情况下,一般只存在一个同步任务,如果是注册到 Eureka 的业务服务需要同步到 Nacos ,那就有一个 Eureka -> Nacos 的同步任务,反之亦然。在平滑迁移中,一个业务服务一部分实例在 Eureka 上,另一部分实例在 Nacos 上,那么会产生两个双向同步的任务。
    4、一个业务服务的同步方向,是根据业务服务实例元数据( Metadata )的标记 syncSource 来决定。

    Nacos Eureka Sync 问题痛点

    Nacos Eureka Sync 同步节点需要代理业务服务实例和 Nacos Server 间的心跳上报。

    Nacos Eureka Sync 将心跳上报请求放入队列,以固定线程消费,一个同步业务服务节点处理的服务实例数超过一定的阈值会造成业务服务实例的心跳发送不及时,从而造成业务服务实例的意外丢失。

    Nacos Eureka Sync 节点宕机,上面处理的心跳任务会全部丢失,会造成线上调用大面积失败,后果不堪设想。

    Nacos Eureka Sync 已经开始工作的时候,从 Eureka 或者 Nacos 上,新上线或者下线一个业务服务(非实例),都需要让 Nacos Eureka Sync 实时感知。

    Nacos Eureka Sync 架构思想

    image.png
    1、从各个注册中心获取业务服务列表,初始化业务服务同步任务列表,并持久化到 Etcd 集群中。
    2、后续迁移过程增量业务服务通过 API 接口持久化到 Etcd 集群中,业务服务迁移过程整合 DevOps 发布平台。整个迁移过程全自动化,规避人为操作造成的遗漏。
    3、同步服务订阅 Etcd 集群获取任务列表,并监听同步集群的节点状态。
    4、同步服务根据存活节点的一致性 Hash 算法,找到处理任务节点,后端接口通过 SLB 负载均衡,删除任务指令轮询到的节点。如果是自己处理任务则移除心跳,否则找到处理节点,代理出去。
    5、同步服务监听源注册中心每个业务服务实例状态,将正常的业务服务实例同步到目标注册中心,保证双方注册中心的业务服务实例状态实时同步。
    6、业务服务所有实例从 Eureka 到 Nacos 后,需要业务部门通知基础架构部手动从 Nacos Eureka Sync 同步界面摘除该同步任务。

    Nacos Eureka Sync 方案实现

    image.png
    基于官方的 Nacos Sync 做任务分片和集群高可用,目标是为了支持大规模的注册集群迁移,并保障在节点宕机时,其它节点能快速响应,转移故障。技术点如下,文中只列出部分源码或者以伪代码表示:
    **详细代码,请参考:
    https://github.com/zhangmen-tech/nacos**

    服务一致性 Hash 分片路由:
    根据如图1多集群部署,为每个节点设置可配置的虚拟节点数,使其在 Hash 环上能均匀分布。

    // 虚拟节点配置
    sync.consistent.hash.replicas = 1000;

    // 存储虚拟节点
    SortedMap circle = new TreeMap();
    // 循环添加所有节点到容器,构建Hash环
    replicas for loop {

    // 为每个物理节点设置虚拟节点
    String nodeStr = node.toString().concat("##").concat(Integer.toString(replica));
    // 根据算法计算出虚拟节点的Hash值
    int hashcode = getHash(nodeStr);
    // 将虚拟节点放入Hash环中
    circle.put(hashcode, node);

    // 异步监听节点存活状态
    etcdManager.watchEtcdKeyAsync(REGISTER_WORKER_PATH, true, response -> {

    for (WatchEvent event : response.getEvents()) {
    // 删除事件,从内存中剔除此节点及Hash中虚拟节点
    if (event.getEventType().equals(WatchEvent.EventType.DELETE)) {
        String key = Optional.ofNullable(event.getKeyValue().getKey()).map(bs -> bs.toString(Charsets.UTF_8)).orElse(StringUtils.EMPTY);
        //获取Etcd中心跳丢失的节点
        String[] ks = key.split(SLASH);
        log.info("{} lost heart beat", ks[3]);
        // 自身节点不做判断
        if (!IPUtils.getIpAddress().equalsIgnoreCase(ks[3])) {
            // 监听心跳丢失,更显存货节点缓存,删除Hash环上节点
            nodeCaches.remove(ks[3]);
            try {
                // 心跳丢失,清除etcd上该节点的处理任务
                manager.deleteEtcdValueByKey(PER_WORKER_PROCESS_SERVICE.concat(SLASH).concat(ks[3]), true);
            } catch (InterruptedException e) {
                log.error("clear {} process service failed,{}", ks[3], e);
            } catch (ExecutionException e) {
                log.error("clear {} process service failed,{}", ks[3], e);
            }
        }
    }

    }

    根据业务服务名的 FNV1_32_HASH 算法计算每个业务服务的哈希值,计算该 Hash 值顺时针最近的节点,将任务代理到该节点。

    // 计算任务的Hash值
    int hash = getHash(key.toString());
    if (!circle.containsKey(hash)) {

    SortedMap<Integer, T> tailMap = circle.tailMap(hash);
    // 找到顺势针最近节点
    hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();

    }

    // 得到Hash环中的节点位置
    circle.get(hash);

    // 判断任务是否自己的处理节点
    if (syncShardingProxy.isProcessNode(taskDO.getServiceName())) {

    //如果任务属于该节点,则进行心跳同步处理
    processTask(Task);

    }

    // 删除心跳同步任务
    if (TaskStatusEnum.DELETE.getCode().equals(taskUpdateRequest.getTaskStatus())) {

    // 通过Etcd存活节点的一致性Hash算法,获取此任务所在的处理节点
    Node processNode = syncShardingProxy.fetchProcessNode(Task);
    if (processNode.isMyself()) {
        // 如果是自己的同步任务,发布删除心跳事件
        eventBus.post(new DeleteTaskEvent(taskDO));
    } else {
        // 如果是其他节点,则通过Http代理到此节点处理
        httpClientProxy.deleteTask(targetUrl,task);
    }

    }

    同步节点宕机故障转移:
    节点监听。监听其它节点存活状态,配置 Etcd 集群租约 TTL , TTL 内至少发送 5 个续约心跳以保证一旦出现网络波动避免造成节点丢失。

    // 心跳TTL配置
    sync.etcd.register.ttl = 30;

    // 获取租约TTL配置
    String ttls = environment.getProperty(ETCD_BEAT_TTL);
    long ttl = NumberUtils.toLong(ttls);

    // 获取租约ID
    long leaseId = client.getLeaseClient().grant(ttl).get().getID();
    PutOption option = PutOption.newBuilder().withLeaseId(leaseId).withPrevKV().build();
    client.getKVClient().put(ByteSequence.from(key, UTF_8), ByteSequence.from(value, UTF_8), option).get();
    long delay = ttl / 6;

    // 定时续约
    scheduledExecutorService.schedule(new BeatTask(leaseId, delay), delay, TimeUnit.SECONDS);

    // 续约任务
    private class BeatTask implements Runnable {

    long leaseId;
    long delay;
    
    public BeatTask(long leaseId, long delay) {
        this.leaseId = leaseId;
        this.delay = delay;
    }
    
    public void run() {
        client.getLeaseClient().keepAliveOnce(leaseId);
        scheduledExecutorService.schedule(new BeatTask(this.leaseId, this.delay), delay, TimeUnit.SECONDS);
    }

    }

    节点宕机。其中某个节点宕机,其任务转移到其它节点,因为有虚拟节点的缘故,所以此节点的任务会均衡 ReSharding 到其它节点,那么,集群在任何时候,任务处理都是分片均衡的,如图2中, B 节点宕机, ##1 、 ##2 虚拟节点的任务会分别转移到 C 和 A 节点,这样避免一个节点承担宕机节点的所有任务造成剩余节点连续雪崩。

    节点恢复。如图3,节点的虚拟节点重新添加到 Hash 环中, Sharding 规则变更,恢复的节点会根据新的 Hash 环规则承担其它节点的一部分任务。心跳任务一旦在节点产生都不会自动消失,这时需要清理其它节点的多余任务(即重新分配给复苏节点的任务),给其它节点减负(这一步非常关键,不然也可能会引发集群的连续雪崩),保障集群恢复到最初正常任务同步状态。

    // 找到此节点处理的心跳同步任务
    Map finishedTaskMap = skyWalkerCacheServices.getFinishedTaskMap();

    // 存储非此节点处理任务
    Map unBelongTaskMap = Maps.newHashMap();

    // 找到集群复苏后,Rehash后不是此节点处理的任务
    if (!shardingEtcdProxy.isProcessNode(taskDO.getServiceName()) && TaskStatusEnum.SYNC.getCode().equals(taskDO.getTaskStatus())) {

    unBelongTaskMap.put(operationId, entry.getValue());

    }

    unBelongTaskMap for loop {

    // 删除多余的节点同步
    specialSyncEventBus.unsubscribe(taskDO);
    
    // 删除多余的节点处理任务数
    proxy.deleteEtcdValueByKey(PER_WORKER_PROCESS_SERVICE.concat(SLASH).concat(IPUtils.getIpAddress()).concat(SLASH).concat(taskDO.getServiceName()), false);
    
    // 根据不同的同步类型,删除多余的节点心跳
    if (ClusterTypeEnum.EUREKA.getCode().equalsIgnoreCase(clusterDO.getClusterType())) {
        syncToNacosService.deleteHeartBeat(taskDO);
    }
    
    if (ClusterTypeEnum.NACOS.getCode().equalsIgnoreCase(clusterDO.getClusterType())) {
        syncToEurekaService.deleteHeartBeat(taskDO);
    }
    
    // 删除多余的finish任务
    finishedTaskMap.remove(val.getKey());

    }

    节点容灾。如果 Etcd 集群连接不上,则存活节点从配置文件中获取,集群正常运作,但是会失去容灾能力。

    // 配置所有处理节点的机器IP,用于构建Hash环
    sync.worker.address = ip1, ip2, ip3;

    // 从配置文件获取所有处理任务节点IP
    List ips = getWorkerIps();
    ConsistentHash consistentHash = new ConsistentHash(replicas, ips);

    // 如果从Etcd中获取不到当前处理节点,则构建Hash环用配置文件中的IP列表,且列表不会动态变化
    if (CollectionUtils.isNotEmpty(nodeCaches)) {

    consistentHash = new ConsistentHash(replicas, nodeCaches);

    }

    return consistentHash;

    Nacos Eureka Sync 保障手段

    Nacos Eureka Sync 同步界面

    从如下界面可以保证,从 Eureka 或者 Nacos 上,新上线或者下线一个业务服务(非实例),都能让 Nacos Eureka Sync 实时感知。但我们做了更进一层的智能化和自动化:

    1、新增同步。结合 DevOps 发布平台,当一个业务服务(非实例)新上线的时候,智能判断它是从哪个注册中心上线的,然后回调 Nacos Eureka Sync 接口,自动添加同步接口,例如,A 业务服务注册到 Eureka 上,DevOps 发布平台会自动添加它的 Eureka -> Nacos 的同步任务,反之亦然。当然从如下界面的操作也可实现该功能。
    2、删除同步。由于 DevOps 发布平台无法判断一个业务服务(非实例)下线,或者已经迁移到另一个注册中心,已经全部完毕(有同学会反问,可以判断的,即查看那个业务服务的实例数是否是零为标准,但我们应该考虑,实例数为零在网络故障的时候也会发生,即心跳全部丢失,所以这个判断依据是不严谨的),交由业务人员来判断,同时配合钉钉机器人告警提醒,由基础架构部同学从如下界面的操作实现该功能。
    image.png

    Nacos Eureka Sync Etcd 监控

    从如下界面可以监控到,业务服务列表是否在同步服务的集群上呈现一致性 Hash 均衡分布。

    image.png

    Nacos Eureka Sync 告警

    Nacos Eureka Sync 告警:

    image.png
    业务服务同步完毕告警:
    image.png

    Nacos Eureka Sync 升级演练

    1、7 月某天晚上 10 点开始, FAT 环境进行演练,通过自动化运维工具 Ansible 两次执行一键升级和回滚均没问题。
    2、晚上 11 点 30 开始,执行灾难性操作,观察智能恢复状况, 9 台 Nacos Eureka Sync 挂掉 3 台的操作,只丢失一个实例,但5分钟后恢复(经调查,问题定位在 Eureka 上某个业务服务实例状态异常)。
    3、晚上 11 点 45 开始,继续挂掉 2 台,只剩 4 台,故障转移,同步正常。
    4、晚上 11 点 52 开始,恢复 2 台,Nacos Eureka Sync 集群重新均衡 ReHash ,同步正常。
    5、晚上 11 点 55 开始,全部恢复,Nacos Eureka Sync 集群重新均衡 ReHash ,同步正常。
    6、12 点 14 分,极限灾难演练, 9 台挂掉 8 台,剩 1 台也能抗住,故障转移,同步正常。
    7、凌晨 12 点 22 分,升级 UAT 环境顺利。
    8、凌晨 1 点 22 ,升级 PROD 环境顺利。

    容灾恢复中的 ReHash 时间小于 1 分钟,即 Nacos Eureka Sync 服务大面积故障发生时,恢复时间小于 1 分钟。

    作者信息:

    吴毅挺,掌门技术副总裁,负责技术中台和少儿技术团队。曾就职于百度、eBay 、携程,曾任携程高级研发总监,负责从零打造携程私有云、容器云、桌面云和 PaaS 平台。

    任浩军,掌门基础架构部负责人。曾就职于平安银行、万达、惠普,曾负责平安银行平台架构部PaaS 平台 Halo 基础服务框架研发。10 多年开源经历,Github ID:@HaojunRen,Nepxion 开源社区创始人,Nacos Group Member,Spring Cloud Alibaba & Nacos & Sentinel& OpenTracing Committer。

    参与 Nacos 落地的基础架构部成员,包括:
    童子龙,张彬彬,廖梦鸽,张金星,胡振建,谢璐,谢庆芳,伊安娜

    ]]>
    从微服务到 Serverless | 开源只是开始,终态远没有到来 Fri, 20 Jun 2025 02:20:33 +0800 21.jpg

    开源只是开始,终态远没有到来。

    从微服务开源,到 Serverless 开源,我们正持续将阿里沉淀的技术能力进行开放。

    只是这一次不同的地方在于,借助 Serverless 能力的释放,将帮助前端进入一个崭新的时代,只需写几个函数即可实现后端业务逻辑,推动业务快速上线,让整个前端研发效能大幅提升。

    什么是 Mindway Serverless

    自7月初发布 V1.0 以来,Midway Serverless 已经获得了超过 2.7k 的 star。Midway Serverless 是一套面向 Serverless 的解决方案,包括框架、运行时、工具链、配置规范4个部分,这4部分组合之后,就能提供了一些面向 Serverless 体系的特有能力:

    1、平台间迁移更容易

    通过提供统一的配置规范以及入口抹平机制,让代码在每个平台基本相同;

    扩展不同云平台的运行时 API,不仅能加载通用的平台间扩展,也能接入公司内部的私有化部署方案。

    2、让应用更易维护和扩展

    提供了标准的云平台函数出入参事件定义;

    提供了多套和社区前端 React、Vue 等融合一体化开发的方案;

    使用了 TypeScript 作为基础语言,方便应用扩展和定义;

    提供了完善的 Midway 体系标志性的依赖注入解决方案。

    3、生态更轻量和自由

    函数体系复用 koa 的生态和 Web 中间件能力,在处理传统 Web 时更加得心应手;

    提供 egg 组件复用 egg 插件的生态链,企业级开发链路更简单顺畅;

    Midway 体系的装饰器能力统一,让传统 Web 迁移到 Serverless 体系更快更好。

    **Midway Serverless @GitHub
    https://github.com/midwayjs/midway**

    防平台锁定

    Vendor Lock-in 是每个使用云平台的的人都会拷问灵魂的问题,Midway Serverless 一开始的初衷就是让一套代码能够运行在不同的平台和运行时之上,我们不建议在不了解全貌时去自定义运行时,那非常的危险。事实上,官方的运行时是运行最稳定,也一定是性能最好的,所有的基准跑分都是基于此。

    我们了解的大多数企业在面对 Serverless 的第一个问题就是,我的代码是不是一定得绑死到阿里云、腾讯云或者 AWS 等等。

    面对这个问题,Midway Serverless 提供了一套 “隐藏式” 入口加上通用化定义来解决这个问题。

    针对每个平台,Midway Serverless 提供了不同的运行时启动器,用于抹平各个平台的差异,并且通过这些启动器,将各个平台的出入参,以及各个 event 结构,网关的返回格式进行规则化,让用户尽可能不感知底层容器以及协议的差异。

    image.png
    除此之外,Midway Serverless 提供了一套 Spec 定义,来抹平多个平台的差异,同时也能方便的在多个平台间复用相同的工具链和函数逻辑。

    image.png
    这样,不管是 API Gateway,还是普通的 HTTP 触发器,都能在统一的编程平面中提供 API,让编写代码变的简单。

    前端赋能

    云 + 端的开发体验是 Midway Serverless 目标之一,传统应用的开发,前端和后端分离,多仓库开发,部署分离。就算使用了 Node.js 的胶水层,也无法避免人员开发体感上的割裂。而在 Serverless 体系下,这不是什么问题。

    由于后端的大幅简化,再加上云服务的 BaaS 化,让数据聚合,页面渲染变的更容易,也能更快的让前端上手和开发。

    一体化慢慢成为了这一块的前端诉求,所谓的一体化,不仅仅是传统仓库的融合,也是整个开发模式的演进,从工程体系加上代码,CI/CD 的整套体系重塑的机会。

    如今的 Midway Serverless,提供了和前端一体的开发方案,囊括了社区现有的 React、Vue 等生态,也对整个工具链(Webpack,ice scrips,umi 等)做了定制化方案,对不同的场景,比如博客等也提供了开箱即用的解决方案。
    image.png
    至于详细的前后端一体化能力,我们后续将单独开一篇文章来介绍前端一体化的细节和思考。

    应用和函数

    Serverless 是未来一段时间的方向,也是前端迈向更高层次的铺路砖。

    之前一直在思索,如今的函数式开发的终态和应用的关系到底是什么?

    现阶段,我们的答案是趋于统一,在被无数次的灵魂拷问和用户需求的追问中,我们得出了这个答案,函数即是应用在当前业务中的最小体现,更简单的来说,是在最小规格容器中运行应用的部分代码。

    之后的一段时间,我们将聚焦于更多平台的接入,以及传统应用的迁移方案上,让之前的用户也能享受到 Serverless 弹性的红利,让企业成本更低,业务上线更容易。

    社区和未来

    在阿里大中台、小前端业务架构日趋深化的背景下,借助其云原生/Serverless 的发展,去年 Node.js 在业务端到端交付场景上看到了未来。

    新一代云 + 端的前台业务交付模式逐渐成为现实,这可以帮助技术团队塑造有业务整体交付能力的特种兵,帮助业务快赢。但其路漫漫仍诸多不完善,为了尽早达到这一步,需要高度聚焦在两个核心问题上:1. 规模化成本、2. 交付速度。

    期望在未来透过我们对规模化成本、交付速度的持续投入,Node.js/Serverless 体系可以体现出全面的先进性。

    生态体系:

    image.png

    社区合作伙伴:

    Koa、Egg、icejs、ykfe/ssr

    test

    本文作者:

    陈仲寅,花名张挺,淘系高级前端技术专家,长期耕耘于 Node.js 技术栈,为淘宝和阿里其他 BU 提供框架和中间件解决方案,阿里集团 Serverless 标准化规范负责人,负责淘宝整体的 Node.js 体系基础建设,解决全栈开发的各种维护和稳定性问题,也同时负责 MidwayJs 系列内部和社区开源产品,包括 Midway、Sandbox、Pandora.js、Injection 等开源产品的开发、维护等工作。

    ]]>
    【升级】8月5日阿里云域名实名认证系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【域名实名认证系统维护通知】

    维护时间:北京时间2020年8月5日 00:00 - 03:00

    维护内容:阿里云域名实名认证系统将于上述时间进行系统维护。

    维护影响:届时域名实名认证(包括命名审核和实名资料审核)和域名信息模板实名认证等相关操作,将无法正常使用,在此期间会对您造成的影响如下:

    1、您提交的域名信息模板实名认证将会失败;

    2、您在单个域名下上传的实名认证资料无法立即提交至审核机构,将长时间处于“上传中”状态,待维护结束后系统自动完成提交;

    3、若您使用已实名认证信息模板注册或购买域名,域名注册或购买成功后,实名认证(包括命名审核和实名资料审核)将长时间处于“处理中”状态,待维护结束后系统自动完成提交;

    4、若您操作域名持有者信息修改(过户)至已实名认证信息模板,将会失败;

    5、域名转入提交实名认证(包括命名审核和实名资料审核)将会延迟,待维护结束后系统自动完成提交;

    6、您通过其他通道(例如API、APP等)提交的域名实名认证(包括命名审核和实名资料审核)将会延迟。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月10日.cc/.tv域名实名认证通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【.cc/.tv域名实名认证通知】

    开始时间:北京时间2020年8月10日 10:00

    升级内容:.cc/.tv域名实名认证系统将于上述时间开放。

    升级影响:届时.cc/.tv域名的注册、转入、持有者信息修改(过户)等操作将要求进行域名实名认证(包括命名审核和实名资料审核),具体要求如下:

    1、新注册.cc/.tv域名若未完成域名实名认证,将被注册局将暂停解析(Serverhold),无法正常使用。

    2、.cc/.tv域名进行持有者信息修改(过户)操作时必须完成实名认证,否则将过户失败。

    3、.cc/.tv域名转入阿里云中国站时,必须选择已实名认证信息模板并等待命名审核结果,若实名认证不通过,将转入失败。

    4、.cc/.tv域名实名认证系统开放后,您可以在“控制台--域名列表--未实名认证域名”列表中查看并提交实名认证。

    存量域名实名认证完成截止时间待注册局进一步通知,请您尽快提交合规资料完成实名认证,以免影响您网站/邮箱或网络应用的正常使用。

    感谢您的理解与支持!


    点此查看:《域名实名认证所需资料》

    点此查看:《域名实名认证操作步骤》

    ]]>
    【升级】8月消息服务MNS升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息服务MNS】【升级通知】

    升级窗口:

    北京时间2020年8月06日 00:00 - 06:00

    北京时间2020年8月13日 00:00 - 06:00

    北京时间2020年8月20日 00:00 - 06:00

    北京时间2020年8月27日 00:00 - 06:00

    升级内容:华北1(青岛)、华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、华东2金融云、华南1金融云、华北2政务云、香港、亚太东南1(新加坡)、亚太东南2(悉尼)、亚太东南5(雅加达)、亚太南部1(孟买)、中东东部1(迪拜)、欧洲中部1(法兰克福)、美国东部1(弗吉尼亚)、美国西部1(硅谷)、英国(伦敦)等地域的消息服务升级。

    升级影响:升级期间MNS相关服务及控制台访问可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可随时通过工单或服务电话95187联系反馈。

    ]]>
    【升级】物联网平台接入IP下线通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【物联网平台】【升级通知】

    升级时间:北京时间2020年9月30日 24:00

    升级内容:物联网平台 218.11.0.64、116.211.167.65、118.178.217.16 共3个接入IP地址将于上述时间下线停止使用。

    升级影响:升级对正常使用DNS域名接入的设备没有影响。物联网平台最新的接入IP地址列表参考公告,建议您不要在设备端写死接入IP地址,物联网平台不保障接入IP地址永久不变。如您需要物联网卡定向流量服务,请提交工单与我们联系。

    由此给您带来的不便,我们表示歉意,感谢您的理解与支持!

    ]]>
    【升级】8月微消息队列MQTT升级公告 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【微消息队列MQTT】【升级通知】

    升级窗口:

    北京时间2020年08月03日 23:00 - 2020年08月04日 07:00

    北京时间2019年08月05日 23:00 - 2020年08月06日 07:00

    北京时间2019年08月10日 23:00 - 2020年08月11日 07:00

    北京时间2019年08月12日 23:00 - 2020年08月13日 07:00

    北京时间2019年08月17日 23:00 - 2020年08月18日 07:00

    北京时间2019年08月19日 23:00 - 2020年08月20日 07:00

    北京时间2019年08月24日 23:00 - 2020年08月25日 07:00

    北京时间2019年08月26日 23:00 - 2020年08月27日 07:00

    北京时间2019年08月31日 23:00 - 2020年09月01日 07:00

    升级内容:所有地域的MQTT服务。

    升级影响:

    升级期间MQTT控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端应用需要设置自动重连,以免影响业务。

    升级期间,消息发送可能会有少量失败,应用做好断连失败重试机制;同时可能会有消息延迟的现象。如需在控制台进行管理操作,请避开维护时间段。

    给您带来的不便敬请谅解,有任何问题,可随时通过工单联系反馈。

    ]]>
    【升级】8月4日Afilias注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    阿里云【域名】【Afilias注册局维护通知】

    维护时间:北京时间2020年8月4日 23:00 - 8月5日 01:00

    维护内容:接到注册局的通知,注册局中国站将于上述时间对后台系统进行维护升级。

    维护影响:届时 .Red/.Kim/.Pro/.Asia/.Info/.Mobi域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【漏洞预警】Nexus Repository Manager 3.x 远程命令执行漏洞(CVE-2020-15871) Fri, 20 Jun 2025 02:20:33 +0800

    2020年8月3日,阿里云应急响应中心监测到Nexus Repository Manager 3.x 版本存在远程命令执行漏洞。


    漏洞描述

    Sonatype Nexus Repository 是一个开源的仓库管理系统,在安装、配置、使用简单的基础上提供了更加丰富的功能。近日Sonatype官方发布安全公告披露了在Nexus Repository Manager 3.x 版本中存在远程命令执行漏洞(CVE-2020-15871),攻击者可在登录后利用该漏洞执行任意命令。漏洞利用需要登录,危害相对较小。阿里云应急响应中心提醒Nexus Repository Manager 3.x 用户尽快采取安全措施阻止漏洞攻击。


    影响版本

    Nexus Repository Manager OSS/Pro version < 3.25.1


    安全建议

    升级到Nexus Repository Manager 3.x 至最新版本 3.25.1


    相关链接

    https://support.sonatype.com/hc/en-us/articles/360052192693-CVE-2020-15871-Nexus-Repository-Manager-3-Remote-Code-Execution-2020-07-29



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.8.3

    ]]>
    100% 展示 MySQL 语句执行的神器-Optimizer Trace -阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在上一篇文章《用Explain 命令分析 MySQL 的 SQL 执行》中,我们讲解了 Explain 命令的详细使用。但是它只能展示 SQL 语句的执行计划,无法展示为什么一些其他的执行计划未被选择,比如说明明有索引,但是为什么查询时未使用索引等。为此,MySQL 提供了 Optimizer Trace 功能,让我们能更加详细的了解 SQL 语句执行的所有分析,优化和选择过程。

    如果您想更深入地了解为什么选择某个查询计划,那么优化器跟踪非常有用。虽然 EXPLAIN 显示选定的计划,但Optimizer Trace 能显示为什么选择计划:您将能够看到替代计划,估计成本以及做出的决策。本篇文章会详细讲解 Optimizer Trace 展示的所有相关信息,并且会辅之一些具体使用案例。

    基于成本的执行计划

    在了解 Optimizer Trace 的之前,我们先来学习一下 MySQL 是如何选择众多执行计划的。

    MySQL 会使用一个基于成本(cost)的优化器对执行计划进行选择。每个执行计划的成本大致反应了该计划查询所需要的资源,主要因素是计算查询时将要访问的行数。优化器主要根据从存储引擎获取数据的统计数据和数据字典中元数据信息来做出判断。它会决定是使用全表扫描或者使用某一个索引进行扫描,也会决定表 join的顺序。优化器的作用如下图所示。

    image

    优化器会为每个操作标上成本,这些成本的基准单位或最小值是从磁盘读取随机数据页的成本,其他操作的成本都是它的倍数。所以优化器可以根据每个执行计划的所有操作为其计算出总的成本,然后从众多执行计划中,选取成本最小的来最终执行。

    既然是基于统计数据来进行标记成本,就总会有样本无法正确反映整体的情况,这也是 MySQL 优化器有时做出错误优化的重要原因之一。

    Optimizer Trace 的基本使用

    首先,我们来看一下具体如何使用 Optimizer Trace。默认情况下,该功能是关闭的,大家可以使用如下方式打开该功能,然后执行自己需要分析的 SQL 语句,然后再从 INFORMATION_SCHEMA 的 OPTIMIZER_TRACE中查找到该 SQL 语句执行优化的相关信息。

    # 1. 打开optimizer trace功能 (默认情况下它是关闭的):
    SET optimizer_trace="enabled=on";
    SELECT ...; # 这里输入你自己的查询语句
    SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
    # 当你停止查看语句的优化过程时,把optimizer trace功能关闭
    SET optimizer_trace="enabled=off";

    这个 OPTIMIZER_TRACE 表有4个列,如下所示:

    • QUERY:表示我们的查询语句。
    • TRACE:表示优化过程的JSON格式文本。
    • MISSING_BYTES_BEYOND_MAX_MEM_SIZE:由于优化过程可能会输出很多,如果超过某个限制时,多余的文本将不会被显示,这个字段展示了被忽略的文本字节数。
    • INSUFFICIENT_PRIVILEGES:表示是否没有权限查看优化过程,默认值是0,只有某些特殊情况下才会是1,我们暂时不关心这个字段的值。

    其中,信息最多也最为重要的就是第二列 TRACE,它也是我们后续分析的重点。

    TRACE 列的基本格式

    TRACE 列的内容是一个超级大的 JSON 数据,直接展开然后一条一条解析估计能看到大伙脑壳疼。

    image

    所以,我们先来看一下这坨大 JSON 的骨架。它有三大块内容,也代表着 SQL 语句处理的三个阶段,分别为准备阶段,优化阶段和执行阶段。

    image

    接下来,我们详细介绍一个案例,在案例中介绍涉及到的具体字段和含义。

    为什么查询未走索引而是全表扫描

    首先,SQL 语句查询不使用索引的情况有很多,我们这里只讨论因为基于成本的优化器认为全表查询执行计划的成本低于走索引执行计划的情况。

    如下图这个场景,明明 val 列上有索引,并且 val 现存值也有一定差异性,为什么没有使用索引进行查询呢?

    image

    我们按照上文使用 Optimizer Trace 找到其 join_optimization 中 range_analysis 相关数据,它会展示 where 从句范围查询过程中索引的选择情况

    image

    由上图可以看出,MySQL 对比了全表扫描和使用 val 作为索引两个方案的成本,最后发现虽然全表扫描需要扫描更多的行,但是成本更低。所以选择了全表扫描的执行方案。

    这是为什么呢?明明使用 val 索引可以少扫描 4 行。这其实涉及 InnoDB 中使用索引查询数据行的原理。

    Innodb引擎查询记录时在无法使用索引覆盖(也就是需要查询的数据多与索引值,比如该例子中,我要查name,而索引列是 val)的场景下,需要做回表操作获取记录的所需字段,也就是说,通过索引查出主键,再去查数据行,取出对应的列,这样势必是会多花费成本的。

    所以在回表数据量比较大时,经常会出现 Mysql 对回表操作查询代价预估代价过大而导致不使用索引的情况。

    一般来说,当SQL 语句查询超过表中超过大概五分之一的记录且不能使用覆盖索引时,会出现索引的回表代价太大而选择全表扫描的现象。且这个比例随着单行记录的字节大小的增加而略微增大。

    通过 range_analysis 中的相关数据也可以对 where 从句使用多个索引列,如何选择执行时使用的索引的情况进行分析。

    小节

    终于,介绍了有关于 MySQL 语句执行分析的 explain 和 Optimizer Trace,下一篇,我们将分析具体的死锁场景。

    个人博客,欢迎来玩

    ]]>
    SpringBoot2 整合MinIO中间件,实现文件便捷管理-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、MinIO简介

    1、基础描述

    MinIO是一个开源的对象存储服务。适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

    MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

    2、存储机制

    MinIO使用按对象的嵌入式擦除编码保护数据,该编码以汇编代码编写,可提供最高的性能。MinIO使用Reed-Solomon代码将对象划分为n/2个数据和n / 2个奇偶校验块-尽管可以将它们配置为任何所需的冗余级别。 这意味着在12个驱动器设置中,将一个对象分片为6个数据和6个奇偶校验块。即使丢失了多达5个((n/2)–1)个驱动器(无论是奇偶校验还是数据),仍然可以从其余驱动器可靠地重建数据。MinIO的实现可确保即使丢失或无法使用多个设备,也可以读取对象或写入新对象。最后,MinIO的擦除代码位于对象级别,并且可以一次修复一个对象。

    二、MinIO环境搭建

    1、安装包下载

    https://dl.min.io/server/minio/release/linux-amd64/minio

    建议使用某雷下载,速度会快点,下载包上传到/opt/minioconfig/run目录下。

    2、创建数据存储目录

    mkdir -p /data/minio/data

    3、服务启动

    启动并指定数据存放地址

    /opt/minioconfig/run/minio server /data/minio/data/

    输出日志

    Endpoint:  http://localhost:9000  http://127.0.0.1:9000    
    AccessKey: minioadmin 
    SecretKey: minioadmin

    这里就是登录地址和账号密码。

    三、整合SpringBoot环境

    1、基础依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>3.0.12</version>
    </dependency>

    2、基础配置

    配置要素:地址和端口,登录名,密码,HTML存储桶,图片存储桶。

    24-1.jpg

    minio:
      endpoint: http://192.168.72.133:9000
      accessKey: minioadmin
      secretKey: minioadmin
      bucketNameHtml: html
      bucketNameImage: image

    文件上传之后,可以基于文件地址直接访问,但是需要在MinIO中配置文件的读写权限:

    24-2.jpg

    3、配置参数类

    @Component
    @ConfigurationProperties(prefix = "minio")
    public class ParamConfig {
    
        private String endpoint ;
        private String accessKey ;
        private String secretKey ;
        private String bucketNameHtml ;
        private String bucketNameImage ;
        // 省略 get 和 set方法
    }

    4、基于MinIO配置类

    封装MinIO客户端连接工具,文件上传的基础方法,返回文件在MinIO服务上的URL地址。

    import io.minio.MinioClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import javax.annotation.PostConstruct;
    import javax.annotation.Resource;
    
    @Component
    public class MinIOConfig {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MinIOConfig.class) ;
    
        @Resource
        private ParamConfig paramConfig ;
    
        private MinioClient minioClient ;
    
        /**
         * 初始化 MinIO 客户端
         */
        @PostConstruct
        private void init(){
            try {
                minioClient = new MinioClient(paramConfig.getEndpoint(),
                                              paramConfig.getAccessKey(),
                                              paramConfig.getSecretKey());
            } catch (Exception e) {
                e.printStackTrace();
                LOGGER.info("MinIoClient init fail ...");
            }
        }
    
        /**
         * 上传 <html> 页面
         */
        public String uploadHtml (String fileName, String filePath) throws Exception {
            minioClient.putObject(paramConfig.getBucketNameHtml(),fileName,filePath);
            return paramConfig.getEndpoint()+"/"+paramConfig.getBucketNameHtml()+"/"+fileName ;
        }
    
        /**
         * 上传 <img> 图片
         */
        public String uploadImg (String imgName, String imgPath) throws Exception {
            minioClient.putObject(paramConfig.getBucketNameImage(),imgName,imgPath);
            return paramConfig.getEndpoint()+"/"+paramConfig.getBucketNameImage()+"/"+imgName ;
        }
    }

    5、服务实现

    提供两个基础方法:HTML和图片上传,存储在不同位置。

    import com.minio.file.config.MinIOConfig;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    @Service
    public class UploadServiceImpl implements UploadService {
    
        @Resource
        private MinIOConfig minIOConfig ;
    
        // 上传 <html> ,返回服务器地址
        @Override
        public String uploadHtml(String fileName, String filePath) throws Exception {
            return minIOConfig.uploadHtml(fileName,filePath);
        }
    
        // 上传 <img> ,返回服务器地址
        @Override
        public String uploadImg(String imgName, String imgPath) throws Exception {
            return minIOConfig.uploadImg(imgName,imgPath);
        }
    }

    上传之后,基于浏览器访问接口返回的url,查看效果:

    24-3.jpg

    四、源代码地址

    GitHub·地址
    https://github.com/cicadasmile/middle-ware-parent
    GitEE·地址
    https://gitee.com/cicadasmile/middle-ware-parent
    ]]>
    有redis发布订阅还需要MQ的吗-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文项目地址:https://github.com/longxiaonan/java-sea/tree/master/javasea-nosql/javasea-nosql-redis-subpub-springdata

    发布者

    com.javasea.redis.publish.TestSenderController定时发布信息到redis

    /**
     * 定时器模拟消息发布者
     */
    @EnableScheduling
    @Component
    public class TestSenderController {
        @Autowired
            private StringRedisTemplate stringRedisTemplate;
    
        /** 向redis消息队列index通道发布消息*/
        @Scheduled(fixedRate = 2000)
        public void sendMessage(){
            stringRedisTemplate.convertAndSend("pmp",String.valueOf(Math.random()));
            stringRedisTemplate.convertAndSend("channel",String.valueOf(Math.random()));
        }
    }

    订阅者

    com.javasea.redis.subscribe.RedisMsg接口的两个实现类RedisChannelSub和RedisPmpSub会
    将收到的信息打印到控制台

    public class RedisChannelSub implements RedisMsg {
        @Override
        public void receiveMessage(String message) {
            //注意通道调用的方法名要和RedisConfig2的listenerAdapter的MessageListenerAdapter参数2相同
            System.out.println("这是RedisChannelSub"+"-----"+message);
        }
    }
    public class RedisPmpSub implements RedisMsg{
    
        /**
         * 接收消息的方法
         * @param message 订阅消息
         */
        @Override
        public void receiveMessage(String message){
            //注意通道调用的方法名要和RedisConfig2的listenerAdapter的MessageListenerAdapter参数2相同
    
            System.out.println("这是RedisPmpSub"+"+++++++++++++++++"+message);
        }
    }
    /**
     * @Description 普通的消息处理器接口
     * @Author longxiaonan@163.com
     * @Date 23:50 2020/7/21 0021
     **/
    @Component
    public interface RedisMsg {
    
        public void receiveMessage(String message);
    }

    路由配置

    RedisConfig2要配置listner和topic的路由,topic中的channel和TestSenderController的channel要对应

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //订阅了一个叫pmp和channel 的通道,多通道
        container.addMessageListener(listenerAdapter(new RedisPmpSub()),new PatternTopic("pmp"));
        container.addMessageListener(listenerAdapter(new RedisChannelSub()),new PatternTopic("channel"));
        container.addMessageListener(listenerAdapter(new RedisChannelSub()),new PatternTopic("flowMsgChennel"));
        //这个container 可以添加多个 messageListener
        return container;
    }
     /**
         * 配置消息接收处理类
         * @param redisMsg  自定义消息接收类
         * @return
         */
        @Bean()
        @Scope("prototype")
        MessageListenerAdapter listenerAdapter(RedisMsg redisMsg) {
            //这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
            //也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage 可以自己到源码里面看
            return new MessageListenerAdapter(redisMsg, "receiveMessage");//注意2个通道调用的方法都要为receiveMessage
        }

    启动程序

    控制台输出:

    2020-07-29 11:42:10.716 ERROR 11776 --- [   container-13] o.s.d.r.l.RedisMessageListenerContainer  : Connection failure occurred. Restarting subscription task after 5000 ms
    这是RedisPmpSub+++++++++++++++++0.6018044162751559
    这是RedisChannelSub-----0.6492059008427755
    这是RedisPmpSub+++++++++++++++++0.14009953778676876
    这是RedisChannelSub-----0.5201275445287328
    这是RedisPmpSub+++++++++++++++++0.2196083162392929
    这是RedisChannelSub-----0.3903862134377962
    这是RedisPmpSub+++++++++++++++++0.5297280660628917

    本文项目地址:https://github.com/longxiaonan/java-sea/tree/master/javasea-nosql/javasea-nosql-redis-subpub-springdata

    ]]>
    阿里云“万仓计划”重磅发布,助力每个企业构建属于自己的云原生数据仓库-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 阿里云新一代云原生数据仓库AnalyticDB全新升级发布会强势来袭!点击以下超链接,观看精彩回放!
    新一代云原生数据仓库AnalyticDB 全新升级发布会

    7月23日,”万仓计划”发布会由阿里云智能数据库事业部总负责人、达摩院数据库首席科学家李飞飞和阿里云智能数据库产品事业部OLAP产品部研究员占超群隆重发布。
    随着数字化进程的推进,企业都面临着挖掘数据价值以驱动业务发展和创新的核心诉求。此次阿里云推出“万仓计划”,意在助力每个企业构建属于自己的数据仓库,助力客户实现低成本数据按需存储、实时在线分析和业务洞察。

    “万仓计划”推出“免费实时一键建仓服务”、“1元体验的云原生数据仓库”、“完善的开发者生态及应用生态”等一整套方案,助力企业低成本、低门槛构建属于自己的数据仓库。

    基于一键建仓功能,AnalyticDB可帮助用户一键将全网数据实时集成到数据仓库中;基于“1元体验的云原生数据仓库”、“开发者生态及应用生态”可以让用户极低成本完成数据接入到数据开发到数据分析(如QuickBI、QuickAudience、Dataphin)到数据应用(支持多语言)的完整闭环。
    如IDC所述,全球数据正在发生质变,呈现爆炸性增长、实时化、云为主要存储及多样化等新特性,对数据仓库提出云原生、实时分析、实时计算、海量存储及多计算场景等能力诉求。阿里云云原生数据仓库AnalyticDB采用存储计算分离+多副本架构,支持从1个节点到最大5000节点的实时按需弹性扩容,可实现PB级数据存储、查询秒级响应,使业务分析效率从天级提升到分钟级乃至秒级。
    此外,AnalyticDB还解决了离在线任务的混合处理、结构化和非结构化异构数据融合分析,以及大规模系统管理成本指数级增长等问题。
    今年,在数据库与大数据权威评测机构TPC发布的面向复杂分析场景TPC-DS10TB权威基准测试中,AnalyticDB性能指标刷新世界纪录,荣登榜单第一名。
    在TPC权威发布的事务与分析混合负载TPC-H基准测试中,AnalyticDB也刷新了TPC-H 30TB性能榜单世界纪录,相比第二名微软 SQL Server 2019,性能提升290%。同时,AnalyticDB也是目前唯一通过中国信息通信研究院《分布式分析型数据库基础能力专项与性能专项》、《分布式事务型数据库基础能力专项与性能专项》测评双认证的分布式数据库产品。
    AnalyticDB自2015年在云上正式发布以来,客户覆盖游戏、广告、文旅、零售、金融、数字政府等众多行业,助力客户实现精细化运营、精准营销、业务分析洞察等核心场景。在本次发布会上,AnalyticDB产品的经典客户聚水潭、天弘基金也分享了最佳的产品实践。
    聚水潭客户表示,基于阿里云AnalyticDB快速构建在线消费者营销与分析报表系统,构建商家精细化运营核心竞争力;同时,客户表示在线分析报表系统相较于传统平台效率提升10倍。
    天弘基金客户分享了AnalyticDB实时数仓在基金业务场景的应用,随着天弘基金销售业务微服务化的改造和销售数据量的原因,基金销售业务数据大多采用了分库分表等技术,导致实时掌握用户和业务销售情况越来越困难。而AnalyticDB作为数据仓库的引入,多快好省地响应运营、主动营销、数据分析等多方面的需求。
    经过八年打磨的阿里云自研产品AnalyticDB,覆盖阿里巴巴集团的所有核心业务;在数据化运营和传统数仓替换升级领域有众多标杆案例。未来数据库将进入云原生时代,AnalyticDB为云而生,助力业务实现开放化、敏捷化、在线化。
    阿里云新品发布会第102期:新一代云原生数据仓库AnalyticDB 全新升级.jpeg

    ]]>
    Docker中级篇-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Docker镜像理解

    Docker镜像是什么

    镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件

    Docker镜像加载原理

    UnionFs:联合文件系统

    UnionFs(联合文件系统):Union文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,UnionFs联合文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像
    特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

    Docker镜像加载原理

    Docker的镜像实际上由一层一层的UnionFs文件系统组成
    bootfs:主要包含 bootloader和 Kernel,bootloader主要是引导加 kernel,Linux刚启动时会加bootfs文件系统,在 Docker镜像的最底层是bootfs,这一层与我们典型的Linux/Unix系统是一样的,包含bootfs加载器和内核,当bootfs加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs转交给内核,此时系统也会卸载bootfs。
    rootfs:在 bootfs之上,包含的就是典型 Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件,rootfs就是各种不同的操作系统发行版,比如:Ubuntu,、CentOS等等

    简单理解:

     1. 对于Docker安装OS来说:就是Docker使用了Linux本身的bootfs,只需要安装自己所需的rootfs
     2. 对于Docker安装普通镜像来说:就是Docker本身是分层下载镜像,所以可以提取出公共层镜像,进行复用
    

    Docker镜像的特点

    Docker镜像都是只读的,当容器启动时,一个新的可写层加载到镜像的顶部

    这一层就是我们通常说的容器层,容器之下的都叫镜像层

    -

    Commit镜像

    # 提交本地镜像 
    # -a:作者信息 -m:描述信息 容器ID 镜像名称:版本信息
    docker commit -a="test" -m="test" 容器id tomcat01:1.0

    Docker容器数据卷

    什么是容器数据卷

    容器之间可以有一个数据共享的技术!Docker容器中产生的数据,同步到本地!

    这就是卷技术!目录的挂载,将我们容器内的目录,挂载到Linux上面!

    容器数据卷使用命令

    # 命令
    docker run -it -v 主机目录:容器内目录 /bin/bash

    挂载MySQL数据库到Liunx宿主机

    # 1. 下载MySQL
    docker pull mysql
    # 2. 启动并挂载 -e:特别注意需要设置密码
    docker run -d -p 3344:3306 -v /home/conf:/etc/mysql/conf.d -v /home/logs:/logs -v /home/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql

    此时我们使用远程连接软件会报错

    -

    # 解决报错
    # 1. 进入容器内
    docker exec -it 容器ID /bin/bash
    # 2. 进入MySQL
    mysql -uroot -p123456
    # 3. 授权
    mysql> GRANT ALL ON *.* TO 'root'@'%';
    # 4. 刷新权限:
    mysql> flush privileges;
    # 5. 更新加密规则:
    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'password' PASSWORD EXPIRE NEVER;
    # 6. 更新root用户密码:
    mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
    # 7. 刷新权限:
    mysql> flush privileges;

    具名和匿名挂载

    -v 容器内路径          # 匿名挂载
    -v 卷名:容器内路径         # 具名挂载
    -v 宿主机路径:容器内路径 # 指定路径挂载

    Docker容器内的卷,在没有指定目录的情况下都在/var/lib/docker/volumes/xxx/_data

    拓展:绑定权限

    # 通过 -v 容器内路径:ro rw 改变读写权限
    ro # readonly 只读
    rw # readwrite 可读可写
    docker run -d nginx01 -v nginxdemo:/etc/nginx:ro nginx
    docker run -d nginx01 -v nginxdemo:/etc/nginx:rw nginx
    # ro:只要看到ro就说明这个路径只能通过宿主机来操作,容器内部是无法操作

    数据卷容器挂载

    目的:实现多个容器间的数据同步(多个MySQL间共享数据)

    # 使用 --volumes-from 容器名称 此命令实现数据卷容器挂载
    docker run -d --name mysql02 -p 3345:3306 --volumes-from mysql01 -e MYSQL_ROOT_PASSWORD=123456 mysql

    DockerFile

    DockerFile是用来构建Docker镜像的文件(命令参数脚本)

    DockerFile构建指令

    FROM                # 基础镜像,一切从这里开始构建
    MAINTAINER             # 镜像是谁写的, 姓名+邮箱
    RUN                    # 镜像构建的时候需要运行的命令
    ADD                    # 步骤,tomcat镜像,这个tomcat压缩包!添加内容 添加同目录
    WORKDIR                # 镜像的工作目录
    VOLUME                # 挂载的目录
    EXPOSE                # 保留端口配置
    CMD                    # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
    ENTRYPOINT            # 指定这个容器启动的时候要运行的命令,可以追加命令
    COPY                # 类似ADD,将我们文件拷贝到镜像中
    ENV                    # 构建的时候设置环境变量!

    创建一个自己的CentOS镜像

    • 创建DockerFile

      vim mycentos
    • 编写DockerFile

      FROM centos
      MAINTAINER MT<1746344046@qq.com>
      
      ENV MYPATH /usr/local
      WORKEDIR $MYPATH
      
      RUN yum -y install vim
      
      EXPOSE 80
      
      CMD /bin/bash
    • 构建自己的镜像

      docker build -f mycentos -t mycentosdemodo:1.0 .
    • 查看镜像生成历史

      docker history 镜像ID

    创建Tomcat镜像

    1.准备镜像文件:Tomcat和JDK的压缩包

    2.编写DockerFile

    FROM centos
    MAINTAINER fortuneteller<1746344046@qq.com>
    
    COPY README.txt /usr/local/README.txt
    
    ADD jdk-8u251-linux-x64.tar.gz /usr/local
    ADD apache-tomcat-9.0.35.tar.gz /usr/local
    
    RUN yum -y install vim
    
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    
    ENV JAVA_HOME /usr/local/jdk1.8.0_251
    ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.35
    ENV CATALINA_BASH /usr/local/apache-toacat-9.0.35
    ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
    
    EXPOSE 8080
    
    CMD ["/usr/local/apache-tomcat-9.0.35/bin/catalina.sh", "run"]

    3.打包镜像

    # 这里使用的是Dockerfile来明白的脚本,所以省略-f
    docker build -t mytomcat .

    4.启动镜像

    docker run -d -p 3344:8080 --name mttomcat -v /home/fortuneteller/tomcat/test:/usr/local/apache-tomcat-9.0.35/webapps/test -v /home/fortuneteller/tomcat/logs:/usr/local/apache-tomcat-9.0.35/logs mytomcat

    5.测试运行

    6.在宿主机的/home/fortuneteller/tomcat/test目录下创建WEB-INF目录与mt.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>docker</title>
    </head>
    <body>
    ----------welcome------------
    <%="这是一个测试页面"%>
    </body>
    </html>

    7.在WEB-INF目录下编写web.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
        <display-name>test</display-name>
    </web-app>

    8.测试访问

    本地镜像上传阿里云

    1.登陆阿里云容器镜像服务

    2.创建命名空间

    3.创建仓库

    4.设置固定密码

    5.查看自己的推送命令

    6.使用推送命令完成上传

    7.拉取时同理根据阿里云提示命令完成即可


    Docker总结

    -

    Docker部署SpringBoot项目

    1.使用Maven构建获得jar包

    2.编写Dockerfile

    FROM java:8
    
    COPY *.jar /app.jar
    
    CMD ["--server.port=8080"]
    
    EXPOSE 8080
    
    ENTRYPOINT ["java", "jar", "app.jar"]

    3.上传Docker与jar包到Linux服务器

    4.使用命令获得镜像

    docker build -t ideatest .

    5.使用命令查看获取到镜像编号

    docker images

    6.启动镜像

    docker run -d -p 3344:8080 ideatest  # 3344端口需要提前在阿里云放开

    7.测试访问

    在浏览器输入:服务器ip:3344
    ]]>
    SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(可回滚)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 通常一次应用的线上发布就表示了一次新功能的上线。在上线过程中,可能发生一些非预期的情况,如新版本软件有bug,或者功能不达预期,就会影响了线上客户的使用。 为了尽快减少对线上用户的影响,发布系统需要提供回滚到前一个或前几个版本的能力。达到快速恢复线上业务的目的。

    从应用的部署变更层次来看,可以分为以下三层:

    1.png

    所以对应了以下的回滚场景:

    • 回滚应用内的配置,适用于由于应用配置变更导致的问题。此时如果应用能够实现动态的配置加载,通过回滚配置就能实现业务恢复的目的。否则需要重启应用
    • 回滚应用代码的版本,适用于代码修改导致的问题。此时需要回滚代码的版本(镜像),重启应用。
    • 回滚应用的工作负载与运维配置(基础设施层)。

    应用内配置回滚

    应用内的配置通常与应用系统需要或业务逻辑配置有关,如配置数据库的连接信息,业务规则配置等,配置的变更也很容易造成线上系统的问题,一般的做法是通过configmap或properties配置文件来实现,这种情况下很难做到动态推送和回滚的能力,因为回滚需要保存不同版本的配置。

    通过 分布配置管理(ACM)(EDAS默认支持)很容易实现配置的集中管理,回滚和灰度,分布式推送,审计等功能。可以在ACM或EDAS的控制台上实现一键回滚,如下图所示:

    2.png

    应用代码回滚

    Deployment是一种常见部署的应用的workload,回滚代码其实对应了回滚对应代码版本的镜像,其实就是对应就是Deployment的回滚,Kubernetes可以通过以下方式支持Deployment的回滚。

    Deployment回滚

    在一个标准的 Kubernetes 体系下,如果出现新版本的pod不能正常工作,需要将deployment回滚到历史版本,Kubernetes 提供了原生的支持;其原理是在默认情况下,kubernetes 会将历史记录保留在系统中,直接使用使用 rollout 命令回滚即可,如下:。

    • 回滚到上一个版本
    kubectl rollout undo deployment.v1.apps/{deployment.name}
    • 回滚到指定的版本

      • 可以通过kubectl rollout history查看历史的版本
    kubectl rollout history  deployment.v1.apps/{deployment.name}
    • 可以通过以下命令回滚到指定的版本
    kubectl rollout undo deployment.v1.apps/{deployment.name} --to-revision={version}

    EDAS中应用回滚

    而在 EDAS 中,结合了原生的能力做了更丰富的白屏的体验,我们就发布过程中发布完成后两个场景分别描述。

    发布过程中回滚

    发布过程中回滚是指在应用发布过程中,就发现了问题,需要将应用回滚到前一个版本。此时的操作就是中断发布流程,将已经升级完成后或正在升级的服务器回滚到前一个版本。

    EDAS在每次变更时候,可以直接中断发布流程,一键回滚。如下图所示:

    3.png

    另外,EDAS发布系统提供单批,分批,金丝雀灰度等多种发布形式,在分批和金丝雀灰度的发布的时候,EDAS还提供不同批次的监控信息,如系统指标,应用指标,应用异常检测等能力,提供快速发现问题能力,如果存在问题,可以立即进行回滚。如下图所示:

    4.png



    我们推荐的方式也是在发布过程中尽量使用分批和金丝雀的能力,以将发布引起的不可用降至最小。

    发布完成后回滚

    发布后回滚是指一次部署过程已经完成,包含部署成功或失败。这个时候,可以通过部署历史的版本来实现回滚的功能。EDAS 默认会存储最多十个部署过的版本,如下图所示:

    5.png

    通过以上的功能,基本上可以覆盖应用在上线过程中需要回滚的场景。减少由于系统发布出问题,造成系统功能使用上的影响。

    应用自动回滚

    从上面的介绍,可以看到回滚的操作都是人工进行的,其实在一些场景里,可以根据一些监控指标,如CPU,load,内存等维度,快速发现问题,就能做到自动回滚,可以能够更快地恢复系统。在 Kubernetes 的体系中,Flagger(https://github.com/weaveworks/flagger) 就是能够实现自动回滚的一个很好的工具。

    应用工作负载和运维配置回滚

    上面介绍了应用内配置和应用代码回滚的方式,在常见的变更中,还存在工作负载及运维配置的变更,如更改工作负载的类型,变更JVM参数,日志配置, 弹性伸缩等。其中JVM参数等通常可以随Deployment进行回滚,但是类似Kubernetes service,日志,弹性伸缩规则等这些基础设施和运维相关的能力回滚就很难做到了。需要将应用的代码,工作负载,运维配置等实现配置化来实现回滚的能力。

    这里推荐阿里巴巴与微软联合提出的OAM(Open Application Model)的规范,他定义了应用的统一交付模型.

    6.png


    在OMA中,一个应用程序包含以下几个核心的理念:

    • Component: 是指应用中的组件,可以是应用运行所依赖的服务如MySQL数据库等,也可以是应用的本身,如Spring cloud的服务提供者。可以通过Component的定义规范来编写一个组件。
    • Trait:是指应用的运维特征,描述了应用部署在具体环境中的运维特征,比如弹性伸缩规则和Ingress配置等,这些运维特征会应用到具体的组件上。
    • Applicationconfiguration:是将Components和traits组装成一个真正能运行起来应用的定义。这个配置文件就是OAM规范中的一个声明式是API,通过它就能实例化出对应的,真实运行的应用。

    一个OAM的应用例子如下:

    apiVersion: core.oam.dev/v1alpha2
    kind: ApplicationConfiguration
    metadata:
      name: springcloud-provider-deployment
      annotations:
        version: v1.0.0
        description: "Description of this deployment"
    spec:
      components:
        - componentName: springcloud-provider-component
          parameterValues:
            - name: PARAMETER_NAME
              value: SUPPLIED_VALUE
            - name: ANOTHER_PARAMETER
              value: "AnotherValue"
          traits:
            - name: manualscaler.core.oam.dev
              version: v1
              spec:
                replicaCount: 3
          scopes:
            - scopeRef:
                apiVersion: core.oam.dev/v1alpha2
                kind: NetworkScope
                name: example-vpc-network

    通过OAM的ApplicationConfiguration这份配置,就能描述线上真正运行的应用,结合能将配置版本化的系统,如Git,ACM等,采用对应的ApplicationConfiguration的版本,就能实现应用的一键回滚。EDAS里就是通过OAM规范来管理Kubernetes的应用,结合OAM声明式API的方式,EDAS里将会实现多种应用回滚的场景,为线上业务保驾护航。

    后续及结语

    本章我们介绍了EDAS的发布系统在EDAS Kubernetes集群上进行应用发布的时候,针对一些异常场景,如何进行快速回滚。但是如果出现问题,如何快速找到问题所在,接下来的文章我们将介绍,在EDAS里通过线上里联调来进行问题诊断。

    ]]>
    (泛型)模板设计模式-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 功能 解析excel表格
    新接到一个功能 解析一个excel表格。并存储入库。实现很简单。写完过后看着代码面向过程。完全没有复用,扩展一说。我希望能用这个excel解析使用于所有这种类型的excel表格。

    设计思路:
    使用模板模式将excel表格的解析过程单独抽出来,创建一个独立的类。解析成map,使用Java反射来创建成对应的对象,然后集合存储后返回

    实现思路:
    在创建的独立类上加上一个泛型用于接收需要去转换的对象实列。创建一个变量去获取泛型的实例类,用于Java反射转换

    代码:实现类

    /**
    * BaseEntity 是公共字典实体类,限定 T 必须为继承
    * BaseEntity 有实体类,避免类型传入错误
    */
    public class ExcelPoiWordAnalysis<T extends BaseEntity>{
    
        protected Class<?> tClass = currentModelClass(getClass()) ;
    
    
        /**
         *  获取泛型对象
         * @param clazz
         * @return
         */
        protected static Class<?> currentModelClass(Class<?> clazz) {
            // 获取超类
            Type type = clazz.getGenericSuperclass();
            if (!(type instanceof ParameterizedType)){
                throw new RuntimeException("超类没有参数化类型");
            }
            // 获取超累泛型上设置的类
            Type trueType = ((ParameterizedType) type).getActualTypeArguments()[0];
            if (!(trueType instanceof Class)) {
                throw new RuntimeException("没有在超累泛型上设置实际的类");
            }
    
            return  (Class<?>) trueType;
        }
    
    
        @Override
        public List<T> analysis(MultipartFile file) {
            解析方法
        }
    
        public List<T> taxWorkBook(Workbook workBook){
          poi解析方法实现
        }
    
    
        public T transition( Map<String, Object> map) {
            // 反射方法
          
        }
    

    逻辑处理类:

    /**
    * 继承ExcelPoiWordAnalysis 传入要解析的对象
    * 就可以得到解析后的对象集合
    */
    @Service
    public class BydjAnalysisServiceImpl extends ExcelPoiWordAnalysis<Entity>  {
    
    
        @Transactional(rollbackFor = Exception.class)
        @Override
        public int wordAnalysis(MultipartFile file)  {
            List<BydjEntity> analysis = analysis(file);
            //   添加逻辑处理类
            return 1;
        }
    
    
    }

    实体类:

    
    /**
    *BaseEntity  是统一字段的公共类就不展示了
    */ 
    public class Entity extends BaseEntity {
    
        private static final long serialVersionUID=1L;
    
    
    
    }

    模板父类ExcelPoiWordAnalysis。实现 解析和对象转换的解耦。ExcelPoiWordAnalysis只需要完成解析的功能和反射的对象转换。具体解析的文档格式和转换的对象实体则根据子类的传入来定义。当不同不是的excel格式和不同对象时。只需要创建新的子类来继承ExcelPoiWordAnalysis就可以完成文档的解析而子类只需要关注数据库逻辑的处理。

    ]]>
    了解Spring R2DBC的声明式事务实现机制-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Spring非反应式事务实现原理

    Spring基于注解和AOP的声明式事务(@Transactional)已经是业务开发的常用工具,默认是采用同步的方式基于ThreadLocal(保存连接信息和会话信息等)实现,在具体数据库操作时就使用同一个数据库连接,并手动提交事务,保证数据正确性。

    基于反应式的Spring事务有何不同

    Spring的反应式实现是基于Reactor框架,该框架对异步编程做了高度的抽象化,主动的线程切换只能通过publishOn/subscribeOn跟换线程池,导致在同步场景表现出色的ThreadLocal无法满足全异步化的事务信息存储需求。Reactor 3提供了一种叫做Context的数据结构,用来替代Threadlocal。

    Context的传播机制

    整体上Context类非常类似一个不可变的Map<Object, Object>,采用CopyOnWrite策略,绑定在每一个订阅者上。但是,context传播具体是怎么实现的呢?有一个简单的例子:

    Flux.just(1, 2, 3)
        .flatMap(x -> Mono.subscriberContext()
            .map(context -> String.format("%s%d", context.get("msg"), x))
        ).subscriberContext(context -> context.put("msg", "no."))
        .subscribe(System.out::println);

    从代码可以看到是通过subscriberContext方法直接put数据,但是这个Context对象是什么时候创建的呢?查看subscriberContext方法的源码发现方法会创建一个FluxContextStart对象,该对象是InternalFluxOperator的子类,实现了subscribeOrReturn(被订阅时调用),在其中将上下文的操作应用到订阅者已有的上下文,而大多数订阅者初始上下文都是Context.empty()。

    final class FluxContextStart<T> extends InternalFluxOperator<T, T> implements Fuseable {
    ....
        @Override
        public CoreSubscriber<? super T> subscribeOrReturn(CoreSubscriber<? super T> actual) {
            Context c = doOnContext.apply(actual.currentContext());
            return new ContextStartSubscriber<>(actual, c);
        }
    ....
    }

    从FluxContextStart的实现可见,Context是由CoreSubscriber的实例所持有,因此Context的传播实际是订阅者实例的传播。而为了避免因为不同操作导致的并发问题,对订阅者的操作都是采用装饰者模式包装一个新的实例,类似Spark RDD的形式。

    public final void subscribe(Subscriber<? super T> actual) {
        CorePublisher publisher = Operators.onLastAssembly(this);
        CoreSubscriber subscriber = Operators.toCoreSubscriber(actual);
    
        try {
            ...
            publisher.subscribe(subscriber);
        }
        catch (Throwable e) {
            Operators.reportThrowInSubscribe(subscriber, e);
            return;
        }
    }

    基于Flux的subscribe方法可见,每次订阅时都会向上游发布者传递订阅者实例,因此Context是自底向上传播。

    Spring R2DBC的事务实现

    基于对常规声明式事务的认识,找到TransactionAspectSupport#invokeWithinTransaction方法,这个方法定义了声明的事务具体要路由到哪个事务管理器执行。自Spring 5.2 M2之后,Spring开始支持反应式事务,在invokeWithinTransaction方法内可以看到如下代码:

    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
        // 从缓存中获取已经加载的反应式事务管理器
        ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
            if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
                throw new TransactionUsageException(
                        "Unsupported annotated transaction on suspending function detected: " + method +
                        ". Use TransactionalOperator.transactional extensions instead.");
            }
            // 根据返回值类型获取适配器
            ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
            if (adapter == null) {
                throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                        method.getReturnType());
            }
            return new ReactiveTransactionSupport(adapter);
        });
        // 执行事务
        return txSupport.invokeWithinTransaction(
                method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
    }

    再继续跟进到反应式的txSupport.invokeWithinTransaction里面,会将相关的事务管理器和事务设置等信息放入上文说到的Context中。具体的对于R2DBC的数据库反应式事务而言,其主要调用的是TransactionalOperatorImpl#transactional方法:

    @Override
    public <T> Mono<T> transactional(Mono<T> mono) {
        return TransactionContextManager.currentContext().flatMap(context -> {
            Mono<ReactiveTransaction> status = this.transactionManager.getReactiveTransaction(this.transactionDefinition);
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            // Need re-wrapping of ReactiveTransaction until we get hold of the exception
            // through usingWhen.
            return status.flatMap(it -> Mono.usingWhen(Mono.just(it), ignore -> mono,
                    this.transactionManager::commit, (res, err) -> Mono.empty(), this.transactionManager::commit)
                    .onErrorResume(ex -> rollbackOnException(it, ex).then(Mono.error(ex))));
        })
        .subscriberContext(TransactionContextManager.getOrCreateContext())
        .subscriberContext(TransactionContextManager.getOrCreateContextHolder());
    }    

    从代码可以看见,这里也是首先从上下文获取事务信息,保证整个反应式处理的各个操作符都会用到同样的数据库连接,并最终实现声明式事务功能。

    总结

    Spring实现反应式事务本质上基于Reactor的Context传播机制,结合原有事务机制改造出来的,所以总结下来就两个核心点:1、Reactor的Context是一种类似不可变Map,绑定在每个订阅者自底向上传播;2、Spring反应式事务通过Reactor的Context在不同线程池共享。

    参考资料

    ]]>
    开放下载!《DTS控制台入门一本通》-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本书图文结合,内容通俗易懂,逐层深入,重点内容详细剖析。通过作者透彻而睿智的描述,使读者在阅读本书时可以更加快速的了解到所需的DTS知识和技术。

    点击免费下载
    《DTS控制台入门一本通》>>>

    本书结构合理,循序渐进。通过阅读本书可以迅速掌握DTS的概念,打下坚实的基础,并养成良好的习惯。

    《DTS控制台入门一本通》的作者,阿里云工程师手辨,具有多年数据库维护与开发工作经验,在本书编写的过程中秉承理论学习与实际开发相结合的原则,力求实现所有技术点和经典案例的完美搭配,以其独有的洞察力和创造力,引导DTS初学者理解这些问题并学会解决方法。

    也可以PC端点击https://developer.aliyun.com/topic/download?id=803 下载

    test

    本书从初学者角度出发,通过通俗易懂的语言、流行有趣的实例,详细地介绍了使用DTS控制台所需要的知识和技术。全书共分 6 章,包括DTS介绍,DTS概览,DTS数据迁移,DTS数据订阅,DTS数据同步和操作日志。书中的知识点都通过有趣而详实的叙述方式娓娓道来,循序渐进地为读者讲解DTS控制台的使用,帮助读者更好地巩固所学知识,提升能力。





    —精彩章节抢先看—

    image.png
    image.png
    image.png
    image.png
    image.png
    image.png



    阿里云开发者社区——藏经阁系列电子书,汇聚了一线大厂的技术沉淀精华,爆款不断。点击链接获取海量免费电子书:https://developer.aliyun.com/topic/ebook
    开发者藏经阁.jpg

    ]]>
    AnalyticDB for MySQL最佳实践总结-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 表设计的最佳实践:

    ADB做为一个分布式的,追求实时分析海量数据的极致性能,需要充分发挥分布式数据库的优势,满足ADB达到最佳性能的特征要求,对表的设计时,需要注意以下几点规则。

    1.选择合适的表类型(维度表or普通表):

    维度表:又称广播表,数据仓库中的一个概念,一般存储一些维度数据。在ADB中建表语句中有DISTRIBUTED BY BROADCAST 的关键字,这些表会在集群的每个节点存储一份数据,因此建议维度表的数据量不宜太大,每张维度表存储的数据不超过10万行。
    普通表:也叫作分区表、事实表,一般存储业务的主题数据。普通表可存储的数据量通常比较大,可以存储千万条甚至万亿条数据,可以对其设置一级分区对数据做sharding或者二级分区进行数据的生命周期管理。

    注意:维度表如果太大会导致数据存储空间的膨胀,节点越多膨胀越大,同时也会导致实时写入时性能下降,iops会比较高。

    2.选择合适的分布键(一级分区键):

    ADB中创建普通表时,默认需要通过DISTRIBUTED BY HASH(column_name,...)指定分布键,按照column_name的HASH值进行分区。ADB支持将多个字段作为分布键。
    分布键的选择依据:
    尽可能选择参与JOIN的字段作为分布键,例如按照用户维度透视或者圈人,可以选择user_id作为分布键。
    尽可能选择值分布均匀的字段作为分布键,例如交易ID、设备ID、用户ID或者自增列作为分布键。
    注意:分布键不均匀容易导致数据分布不均,严重影响写入和查询的效率,此外也容易导致单节点磁盘写满导致整个集群锁定不可用。一般情况数据均匀是第一优先级,然后才考虑JOIN KEY对齐的问题,除非有业务就是想定制化。

    3.选择合适的分区键(二级分区键):

    如果业务明确有增量数据导入需求,创建普通表时可以同时指定分布键和分区,分区可以实现数据的增量同步。
    1)直接用ds的值来做分区 PARTITION BY VALUE(ds)
    2)ds转换后的天做分区 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y%m%d'))
    3)ds转换后的月做分区 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y%m'))
    4)ds转换后的年做分区 PARTITION BY VALUE(DATE_FORMAT(ds, '%Y'))
    注意:一个实例能承载的最大二级分区数目是有限的,当前限制是10240。请提前规划后这个实例的所有表二级分区键,尽量充分利用二级分区,不要让每个二级分区的数据量过小,如:你使用天做二级分区,但是每天数据量很小,这时可考虑用月来做二级分区。否则会导致数据库中需要保存分区数据的元数据特别多,这些元数据是需要存放在内存中,会占据内存较多的空间,容易导致系统的GC或者OOM,同时也会导致实时写入时的iops比较高。

    二级分区的过期策略:
    目前二级分区过期策略是依据大小排序,只保留最大的N个二级分区,其中N为生命周期的大小。假设表A定义的生命周期个数为3,目前存在的二级分区为202001,202002,202003。当分区值为202004的数据写入进来时202001分区就会被淘汰。需要注意的是分区淘汰是延迟进行的,不保证202004的数据写入后立即会淘汰202001。此外在使用二级分区时也要注意脏数据带来的误淘汰问题,如果此时表A分别写入了分区值为300001,300002,300003的三条脏数据,那么分区淘汰策略也会被触发,整表将只剩下分区值最大的三条脏数据。

    4.选择合适的主键:

    在表中定义主键可以去实现数据消重(Replace into)和数据更新操作(Delete、Update)。只有定义过主键的表支持数据更新操作(DELETE和UPDATE)。
    主键的选择依据:
    1)尽可能选择单数字类型字段作为主键,表的性能相对更好。ADB支持将字符串或者多字段组合作为主键。
    2)主键中必须包含分布键和分区键,如果有二级分区键的话,需要包含二级分区键。
    注意:设置的主键的字段不宜太大,或者某个字段的长度不宜过长,否则的话,会导致数据库的IOPS很高。

    5.选择合适聚集索引:

    聚集索引会将该列或者多列排序,保证该列相同或者相近的数据存在磁盘的相同或相近位置,当以聚集列做为查询条件时,查询结果保持在磁盘的相同位置,这样可以减少磁盘的IO。
    聚集索引的选择依据:
    查询一定会携带的字段可以作为聚集索引。例如,电商卖家透视平台中每个卖家只访问自己的数据,卖家ID可以定义为聚集索引,保证数据的局部性,提升数据查询性能。

    注意:目前聚集索引只支持一个,但该聚集索引可以有多列。目前除非对非常分散的数据进行点查,否则聚集索引对性能的帮助很少。

    6.设计合适的数据类型:

    原理:
    ADB处理数值类型的性能远好于处理字符串类型。原因在于:
    1)数值类型定长,占用内存少,存储空间小。
    2)数值类型计算更快,尤其是join时。
    3)从内部索引机制上,字符串类型适合等值查询和范围查询情况,而时间,数值类型性能更高,建议用户尽可能- - 使用数值类型,减少使用字符串类型。
    4)选择尽可能小的列,列类型要尽可能选择匹配的列,比如性别就可以用boolean或者byte类型,数据长度不大的可以用int
    5)在同一个业务模型内,相同字段设计成相同的数据类型和字段长度,字段命名也保持一致,特别是涉及到主外键关联的字段更要注意,避免表在关联时不同的数据类型的字段关联导致隐式转换。

    方法:
    常见将字符串转换为数值类型方法:
    1)包含字符前缀或后缀,例如E12345,E12346等。可以直接去掉前缀或者将前缀映射为数字。
    2)该列只有少数几个值,例如国家名。可以对每个国家编码,每个国家对应一个唯一数字。
    时间/日期类型数据,避免使用varchar字符类型存储,3)尽量使用date,timestamp或者int类型存储时间类型。
    4)对于地理经度维度的使用,需要通过地理函数查询情况,数据类型采用double数据类型。

    数据写入方面的最佳实践:

    实时写入:

    1.批量打包的方式提交:

    向表中写入数据时,可以通过批量打包方式INSERT INTO和REPLACE INTO提高数据写入性能。建议如下:
    1)通过每条INSERT或者REPLACE语句写入的数据行数大于1000行,但写入的总数据量不宜太大,不要超过16MB。
    2)通过批量打包方式写入数据时,单个批次的写入延迟相对会高一些,但是整体的性能会提升。
    3)写入报错时,需要做重试确保数据被写入,重试导致的数据重复可以通过表的主键来消除。
    4)如果不需要对原始的数据进行修改,直接使用insert into比replace into效率会高3倍以上。 样例:

    (id, name,sex,age,login_time) 
    values
    (1,'dcs',0,23,'2018-03-02 10:00:00'),
    (2,'hl',0,23,'2018-03-02 10:01:00'),
    (3,'xx',0,23,'2018-03-02 10:02:00')
    ......;

    2.更新数据

    数据更新有多种方式,使用区别如下:
    • 高频基于主键的行级覆盖更新, 且应用可以补齐所有列,请使用replace into values批量打包
    • 高频基于主键的行级覆盖更新, 应用不能补齐所有列,请使用update into values批量打包
    • 低频基于主键更新,可以使用replace into或者update into单条数据
    • 低频任意条件更新,请使用 update set where
    注意:update需要查表来填补更新中缺失的旧值,因此比replace into多一次查询,性能较低,不建议做高频、大批量的update操作。如果线上update性能无法满足需求,需考虑替换成Replace into,由应用端补旧值。

    3.删除数据

    数据删除有多种方式,使用区别如下:
    • 低频主键条件删除,请使用 delete from where pk = xxx
    • 低频任意条件删除,请使用 delete from where
    • 删除单个二级分区,请使用 truncate partition
    • 删除单表(包括所有二级分区,如有),请使用truncate table或drop table

    批量导入:

    1.如何选择是批量导入还是实时导入

    1)从ODPS、OSS导入ADB,推荐使用insert overwrite select做批量导入,原因有二: 一方面,批量导入适合大数据量导入,性能好 二方面,批量导入适合数仓语义,即导入过程中旧数据可查,导入完成一键切换新数据。如果导入失败,新数据会回滚,不影响旧数据的查询。
    2)从RDS、MySQL、ADB等导入ADB,看数据量情况,数据量不大的(百万级别的表),推荐使用insert into select做实时导入,数据量大的,推荐使用insert overwrite select做批量导入。

    2.导入并发和资源说明

    1)单张表的导入会在系统内部串行,不同表之间的导入任务会并行,默认并行度是2;从ODPS分区表导入到ADB时,每次导入横跨的分区数不要超过30个,同一张表的不同分区导入是排队串行,不同表的导入,同时提交,有并行度n个任务同时导入,出于资源控制,超出的任务也会排队。
    2)导入使用的是ADB内部的资源,与查询一样,属于同一个实例的资源。推荐导入任务在查询qps比较低的时候进行,比如凌晨0点以后,并推荐用户配置d2等定时任务,错峰做导入。

    高效查询的最佳实践

    ADB的优势是能在海量数据场景下,面对复杂查询,做到实时的在线分析。ADB的SQL调优需要充分发挥分布式计算优势,以及ADB本身的一些特征,同时对于通用的数据库优化的方法论同样是适用。

    查询优化的通用法则:

    按照斗佛早些年在《ORACLE DBA手记》上写的文章,数据访问优化满足以下漏斗法则:
    image.png
    1、 减少数据访问(减少磁盘访问)
    例如:尽量多的使用过滤条件,尽早的提前过滤数据,减少参与计算的数据量,能在子查询里面把数据先过滤的提前过滤。
    2、 返回更少数据(减少网络传输或磁盘访问)
    例如:避免select * 的查询,特别的在OLAP数据库下,往往表的列数比较多,同时由于基于列存或者行列混存,对于这种select * 的操作,需要请求的IO回更多。
    3、 减少交互次数(减少网络传输)
    例如:上文提到的批量提交。
    4、 减少服务器CPU开销(减少CPU及内存开销)
    例如:
    A. 减少不必要的排序和分页,特别是在子查询中的排序
    B. 在满足业务前提下,尽量减少count distinct操作
    C. 在满足业务前提下,特别是在海量数据下,采用类似Hyperloglog的近似计算代替准确计算。
    5、 利用更多资源(增加资源)
    例如:
    A. 设计表的时候,尽量避免分区倾斜,导致存储和计算压在某一个节点上,尽量把数据都均匀的散列到所有的节点上,充分利用所有机器的能力,最大发挥分布式的数据库的效能
    B. ADB本身就是MPP大规模并行处理的典型系统,在内核层面也做了大量的优化处理,充分利用更多的资源。

    ADB特殊场景的优化:

    外表的查询最佳实践

    不要尝试对外表进行较为复杂的计算,这样会导致比较严重的GC,因为外表的计算是全部把数据拖过来算的,且网络带宽的压力也会变大。
    同时,外部不支持DML操作(delete,update,truncate),如果要修改外表的数据,需要在源头表里面操作。

    巧妙的使用聚集索引:

    当查询条件一定包含某列时,特别是该列的数据在存储上非常分散时,对该列建立聚集索引,性能会有明显的提升,可以采用类似如下的sql语句添加聚集索引:
    alter table table_name ADD CLUSTERED INDEX index_cls (d_fdbid);
    注意:如果表里面的数据已经有了,直接add cluster index不会对存量的数据排序,需要重建表,在建表的时候加上聚集列关键字。

    减少节点间的数据交互:

    分布式数据库,在充分发挥分布式计算的同时,有时也会加大跨节点间的网络开销,特别是请求的数据散列在各个节点上时,请求的数据量有比较少,且节点个数又比较多情况下,跨网络开销的情况就非常明显,因此可以采用以下几个思路:

    1)如果能采用本地计算,在各个节点内join或者聚合分析时,尽量在本节点内计算,具体做法就是,如果在满足业务前提下,能用户一级分区键关联的,采用一级分区键关联;能对一级分区键进行group by的,采用一级分区键group by,这样可以尽量采用localjoin,大大减少跨网络的访问。
    2)合理的控制节点数量,并不是节点越多越好,当数据库规模不大,且每次查询的数据量很少,且跨网络访问很严重的情况下,节点越多,问题越严重。

    合理的使用索引:

    合理使用索引在数据库调优中,非常重要,在ADB中也不例外。在ADB中,默认每列都会创建索引。但是也有例外情况,如果某列的cardinality值比较少时,通过索引查询可能会更慢,因为他需要多查一次索引再回表,且索引的选择性又不高,性能就会很差,这时可以在建表时把这些disable掉建索引的功能,这样就不会在建表后自动建索引了,如果索引已经创建了,可以把索引删除掉,或者通过hint 不走索引访问:
    alter table table_name drop index index_name 把枚举列的索引删除掉。
    或者使用/+no_index_columns=[t_order_content.fdelete;fdbid]/ 类似这样的hint把索引去掉不走。

    ADB连接的最佳实践

    ADB在使用方式上做到和99%以上和mysql兼容,支持多种连接方式,比如mysql命令行,JDBC连接,Python连接,c#连接,PHP连接等等。整体请参考官方文档 。
    例如:
    采用Druid连接池的配置,请参考。

    FAQ:

    1. 磁盘占用大小包含哪些数据?为什么会触发磁盘满锁定?
    磁盘占用量主要包括数据和索引两部分。索引在构建过程中,会临时额外占用少量空间,期间可能会有少量数据膨胀。
    用户可以使用如下sql查询使用空间:(延迟统计的,1小时统计一次)
    select (sum(data_length)+sum(index_length))/1024/1024/1024 as '数据空间(GB)' from information_schema.tables;
    使用如下sql查询当前日志使用空间:
    show binary logs
    其中,adb-bin.log表示binlog,adb-system.log表示系统日志。
    单节点磁盘使用量超过80%则会触发锁定.
    有2种可能原因:一是一级分区键选择不合理导致某些节点数据倾斜,二是数据分布比较平均,总体使用量过大。是否存在表有分区倾斜可以在控制台页面查看存储的整体水位达到多少。

    2. 是否支持磁盘大小扩缩,是否支持节点数扩缩?节点数扩缩需要多久?
    目前磁盘使用ecs云盘,只支持扩容,不支持缩容。节点数支持扩缩,数量范围与实例初始规格相关,控制台变配页面可以看到当前实例节点数变配范围。节点数扩缩会在节点间进行部分数据迁移,正常情况下最大耗时为最大单节点磁盘使用量/40(MB/s) + 20min。

    3. 如何进一步提高写入性能?
    数据写和导入尽可能使用批量写入的方式,使用dataworks进行数据同步可关注是否并发任务数和写入批大小设置过小。主键选择尽可能精简。写入表的分区键选择尽可能均衡。

    4. 如何选择合适的一级分区列?
    ADB内部将数据拆分为若干个一级分区,通常情况下一个ADB实例内部大概有100数量级左右的一级分区。在进行查询时不同的一级分区并发进行。因此一级分区列最重要的一点是需要保证数据尽可能的均匀,否则会出现长尾查询拖慢整体查询进度。
    不同的表如果一级分区列相同,那么这些表在执行以一级分区列为join key的join时可以大幅度减少数据shuffle。因此在保证数据均匀的前提下,相同的一级分区列可以加速join。

    5. 如何选择合适的二级分区列?
    二级分区是对一级分区的进一步拆分,一般是在时间维度上进行。大部分情况下单二级分区的数据尽量超过一百万,达到百万级,同时也不要达到数千万。
    下面以一张订单表为例来选择合适的二级分区。假设这张表单天增量百万左右,需要保留10年的数据。由于我们单ADB集群通常情况下,有100左右的一级分区。若该表按日为分区,则单二级分区的大小约为1w左右远低于我们的建议值。因此用月或者年作为二级分区比较合适。
    二级分区的生命周期是支持修改的。如下语句: alter table lineitem partitions 12展示了如何将lineitem的二级分区个数修改为12。需要注意的是二级分区个数的修改是后台异步执行的,执行build table lineitem可以加速分区修改任务。

    6. 二级分区的过期策略是怎样的?
    目前二级分区过期策略是依据大小排序,只保留最大的N个二级分区,其中N为生命周期的大小。假设表A定义的生命周期个数为3,目前存在的二级分区为202001,202002,202003。当分区值为20204的数据写入进来时202001分区就会被淘汰。需要注意的是分区淘汰是延迟进行的,不保证20204的数据写入后立即会淘汰202001。此外在使用二级分区时也要注意脏数据带来的误淘汰问题,如果此时表A分别写入了分区值为300001,300002,300003的三条脏数据,那么分区淘汰策略也会被触发,整表将只剩下分区值最大的三条脏数据。

    7. 聚集索引是什么,什么情况下适合使用聚集索引?
    聚集索引就是让数据根据若干字段进行排序。对于有这相同的排序字段的数据在物理上尽可能的存储在一起。
    如果查询一定会带的某个字段,比如电商中卖家透视平台,每个卖家只访问自己的数据,那卖家id就是可以选择为聚集索引,可以保证数据的locality,进而性能有量级的提升。
    目前聚集索引只支持一个,但该聚集索引可以有多列。目前除非对非常分散的数据进行点查,否则聚集索引对性能的帮助很少,请谨慎选择。

    8. 主键如何选择,是否能够修改主键?
    主键一般情况下用于数据的去重。主键的长度与去重的效率成反比,因此非常不建议使用较长的String如UUID作为主键,建议为1~3个long值。
    此外需要注意的是,主键需要包含一级分区键和二级分区键。目前不支持主键的修改。

    9. 如何自己指定索引?

    1. ADB默认是全字段索引,一般不需要自己维护索引。
    2. 如何查看一个表有哪些索引,跟mysql一样使用这个语句:show index from t
    3. 如果想要drop掉某个索引可以使用:alter table t drop key key_name。其中key_name可以通过上面的语句查询。
      注意:drop掉索引会导致查询变慢。
    4. 如果想要自己指定索引,那跟mysql一样,使用key关键字:key key_name (column_name)。如:create table t(id bigint,c1 varchar,key id_idx(id))DISTRIBUTE BY HASH(id)

    10直接用mysql的建表DDL可以在adb中执行建表吗?
    可以的,具体行为是这样的:
    • 如果DDL中有主键,用主键做distribute key
    • 如果DDL中没有主键,会自动给他添加一个字段:__adb_auto_id__,然后用__adb_auto_id__做主键和分区键。

    12. 可以直接用adb2.0的建表语句在adb3.0中执行吗?
    ADB3.0已经兼容了ADB2.0的建表语句。

    ]]>
    从零打造自己Linux工作环境-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 [TOC]

    1. 前言

    我们在工作或学习过程中,有许多时候会接触到 Linux,然后便想要自己亲自试试这个系统,但苦于没有系统学习过,所以想要打造自己的工作环境难免会遇到许多困难。为了防止大家踩我踩过的坑,才有了这篇博客。本博客将主要从 系统设置、常用软件安装、开发环境搭建、系统美化 三个方面着手,手把手教你如何从零打造自己的 Ubuntu (基于 Ubuntu 18.04.4 LTS )工作环境。

    先展示下我自己的工作环境:

    • 桌面

    • 程序坞

    • 文件

    2. 系统设置

    2.1 换镜像源

    即将官方自带的源换成国内镜像源,如阿里、华为、网易等;

    打开 软件和更新 ,然后选择下载自 其他站点,选择你要设置的镜像源站点即可,也可以点击右侧选择最佳服务器,会根据你所处地区然后自动选择网速最快的;

    2.2 安装更新

    换源之后,执行如下命令用于更新系统;

    sudo apt update
    sudo apt-get  upgrade

    2.3 使用本地时区

    使用双系统时,往往两者时间会不正确,可以通过如下命令让 Ubuntu 使用本地时区;

    timedatectl set-local-rtc 1

    2.4 卸载自带的“无用”软件

    此处说的“无用”软件,因人而异,笔者只是提供建议,建议卸载软件列表如下(可能还有些没咋用的没列出,读者可以自行决定是否卸载,卸载之后如果有用到,还可以重新安装);

    • thunderbird 自带邮件
    • rhythmbox 音乐
    • gnome-mahjongg 对对碰游戏
    • gnome-mines 扫雷
    • gnome-sudoku 数独
    • aisleriot 纸牌
    • simple-scan 扫描器
    • onboard 屏幕键盘
    • brasero 光盘可怜工具
    • cheese 茄子相机
    • libreoffice 套件,用 WPS 代替

    3. 常用软件安装

    3.1 输入法

    • 搜狗输入法 for Linux

    软件由搜狗输入法团队和 Ubuntu Kylin 团队共同开发,能够满足我们日常输入需求;

    • 安装

    此处推荐下载安装包后安装,下载地址,下载后的安装包如下;

    然后在当前文件夹下打开终端,输入如下命令安装即可;

    sudo dpkg -i sogoupinyin_2.3.1.0112_amd64.deb 

    接着到 设置 - > 区域和语言 点击已安装的语言;

    把键盘输入法设置为 fcitx,然后应用到整个系统即可,然后注销或者重启即可使用;

    • 注意

    当安装时,可能会报依赖相关问题,可以在执行上述命令后,在终端中输入如下命令修复依赖安装,然后再次执行上面的安装命令即可;

    sudo apt-get --fix-broken install

    3.2 浏览器

    • Chrome

      说到浏览器,虽然 Ubuntu 中预装了 Firefox,但笔者更习惯用 Chrome ,作为全球范围内份额最高的浏览器,自然不用说啥,其实微软新出的 Edge 浏览器也很不错,只是目前还没有支持 Linux,后续可能会进一步支持。
      
    • 安装

    建议使用 deb 安装包安装,先 下载 安装包,然后在本地终端使用如下命令进行安装;

    sudo dpkg -i google-chrome-stable_current_amd64.deb 

    • 成品

    3.3 QQ

    去官网 下载 对应安装包,然后在终端使用如下命令安装;

    sudo dpkg -i linuxqq_2.0.0-b2-1082_amd64.deb

    • 成品

    3.4 微信

    • 安装

    此处推荐使用网页版即可,wine 版虽然网上也有,但有许多 bug,所以推荐使用网页版;

    • 创建快捷方式

    打开网页版微信,然后打开浏览器设置中的 更多工具 -> 创建快捷方式 即可,然后在桌面就会出现快捷方式,信任即可;

    • 成品

    3.5 音乐播放器

    • 网易云音乐

    作为网易旗下的音乐软件,虽然版权问题流失了许多用户,但不影响它越来越受广大用户喜欢。而我们在学习工作之余听听音乐,放松下调整状体也是极好的。

    • 安装

    下载 Linux 版本安装包,然后使用如下命令在终端中安装即可;

    sudo dpkg -i netease-cloud-music_1.2.1_amd64_ubuntu_20190428.deb

    • 成品

    3.6 截图工具

    • flameshot

      flameshot,能够用于标注、模糊,同时还支持上传到 imgur 图床的新式截图工具;
    • 安装
    sudo apt install flameshot

    • 设置快捷键

    依次进入 设置 -> 设备 -> 键盘 ,然后滑到最底端会有一个 + 按钮,点击 + 添加自定义快捷键,并设置名称和命令,然后点击右上角添加就可以设置自己想要的截图快捷键,比如我设置的是 Ctrl + Alt + A,如下图所示;

    • 成品

    3.7 图片编辑器

    • Gimp

    GIMP是 GNU Image Manipulation Program(GNU图像处理程序)的缩写,它是 Peter Mattis 和 Spencer Kimhall 开发的免费照片和图像处理和创作工具,功能十分强大。支持多种图像处理工具、全通道、多级撤销操作恢复旧貌与映像修饰等功能。支持数目众多的效果插件(plug-ins),完全可以与 Windows 平台下著名的图像处理软件 Photoshop 媲美;

    • 安装

    直接使用如下命令安装即可;

    sudo apt-get install gimp

    • 成品

    3.8 文字处理

    • WPS

    Ubuntu 中自带了 LibreOffice 办公套件,但此处更推荐使用 WPS,更加符合我们国人的使用习惯。

    • 安装

    先下载安装包,然后依然在本地终端使用如下命令进行安装;

    sudo dpkg -i wps-office_11.1.0.9505_amd64.deb

    • 成品

    3.9 Markdown 编辑器

    • Typora

    作为一款极简的 Markdown 编辑器,合并了写作和预览。支持表格、代码编辑,拖拽插图等,非常好用;喜欢它的原因也就是:美观、免费,而且跨平台;

    • 安装

    依次使用如下命令即可安装;

    wget -qO - https://typora.io/linux/public-key.asc | sudo apt-key add -
    
    sudo add-apt-repository 'deb https://typora.io/linux ./'
    
    sudo apt-get update
    
    sudo apt-get install typora

    • 成品

    3.10 文档阅读器

    • Okular

    可以说是 Linux 中最好的 PDF 阅读器,而且还支持常用的 CHM、EPub 等格式文档的查看;

    • 安装

    直接在终端中使用如下命令安装即可;

    sudo apt-get install okular

    • 成品

    3.11 思维导图

    • XMind

    做事或者写作之前,都喜欢先做一个总体的思维导图,然后根据思维导图去细化每一部分,而 XMind 正是这么一款工具;

    • 安装

    下载 安装包之后,在终端中使用如下命令行安装即可;

    sudo dpkg -i XMind-2020-for-Linux-amd-64bit-10.1.2-202004142327.deb 

    • 成品
      image.png

    3.12 多媒体播放器

    • VLC

    支持众多音频与视频解码器及文件格式,并支持 DVD 影音光盘,VCD 影音光盘及各类流式协议。也能作为 unicast 或 multicast 的流式服务器在 IPv4 或 IPv6 的高速网络连接下使用。融合了 FFmpeg 计划的解码器与 libdvdcss 程序库使其有播放多媒体文件及加密 DVD 影碟的功能,是 Linux 中多媒体播放器的不二之选;

    • 安装

    直接在终端中使用如下命令安装即可;

    sudo snap install vlc

    • 成品

    3.13 录屏软件

    • Kazam

    小巧而强大,易安装,即可选择区域录制,也可选择全屏录制,同时还兼具截图功能;

    • 安装

    直接在终端中使用如下命令安装即可:

    sudo apt install kazam
    • 成品

    3.14 视频编辑软件

    • OpenShot

    OpenShot 是 Linux 上的一个多用途视频编辑器,可以帮助你创建具有过渡和效果的视频。

    • 安装

    直接在终端中使用如下命令安装即可;

    sudo apt install openshot
    • 成品

    3.15 下载工具

    • uGet

    Uget(原名:Urlgfe)是一个基于 GTK+ 编写的自由和开放源码的下载管理器 ,跨平台(Windows和GNU / Linux的),支持断点续传和分类下载 ,uGet 同时 具备 多线程下载能力;

    • 安装
    sudo apt-get install uget

    • 成品

    4. 开发环境搭建

    4.1 git 安装

    直接在终端中使用如下命令安装即可;

    sudo apt install git

    4.2 Node.js 安装

    • 安装

    从淘宝镜像源 下载 对应版本压缩包,然后解压到你要存储的路径,比如我的是 /home/cunyu/soft/node-v12.16.2

    • 配置

    安装(解压)好后,在 /etc/profile 配置文件中配置;

    export NODEJS_HOME=/home/cunyu/soft/node-v12.16.2
    export PATH=$NODEJS_HOME/bin:$PATH
    • 换淘宝镜像源
    npm config set registry http://registry.npm.taobao.org/
    • 验证
    node -v
    npm -v

    4.3 JDK 安装

    • 安装

    先去 下载 要安装的 JDK 版本,然后解压到本地,移到你要存放的位置;

    tar -zxvf jdk-11.0.7_linux-x64_bin.tar.gz 

    • 配置

    打开配置文件 /etc/profile,然后加入如下内容;

    export JAVA_HOME=/home/cunyu/Soft/jdk11.0.7
    export PATH=$JAVA_HOME:$PATH

    • 验证

    安装和配置之后,在终端中输入如下命令进行验证是否成功;

    # 查看版本
    java -version
    # 编译
    java
    # 执行
    javac

    4.4 MiniConda 安装

    • 安装

    直接去 下载 对应版本安装包,此处以 3.7 版本为例,然后在终端中对齐赋予执行权限后执行;

    chmod +x Miniconda3-latest-Linux-x86_64.sh 
    
    sh Miniconda3-latest-Linux-x86_64.sh 

    执行后会让你阅读相关权限,然后输入 yes 同意,然后会让你指定安装目录,默认就直接回车就好,最后等安装好即可;

    • 配置

    打开 ~/.bashrc 文件,在其中加入如下 内容;

    # 注意输入你自己安装 miniConda 的目录
    export  PATH="/home/cunyu/miniconda3/bin:"$PATH
    • 验证

    4.5 MySQL 安装

    最新版本

    • 安装
    sudo apt-get install mysql-server

    • 设置 root 用户密码

    上述安装过程结束后,直接登录,此时 root 用户没有设密码,需要输入密码时直接回车;

    myslq -u root -p

    登录后,使用如下命令来修改 root 用户密码,退出后再次登录输入你修改的密码即可;

    SET PASSWORD FOR 'root'@localhost = PASSWORD('你要设置的密码');

    指定版本

    • 通过如下命令查看可安装的版本;
    apt-cache search mysql | grep mysql-server
    • 然后利用如下命令安装即可;
    sudo apt install mysql-server-5.7

    注意

    登陆时如果需要加上 sudo 才能登陆,则通过 sudo 登陆后,使用如下命令更新,即可退出后不用再使用 sudo 登陆

    use mysql;
    select User,Host,plugin from user;
    update user set Plugin='mysql_native_password'

    4.6 VS Code 安装

    • 安装

    直接去 下载 对应安装包,然后在终端使用如下命令安装;

    sudo dpkg -i code_1.44.2-1587059832_amd64.deb

    • 成品

    4.7 IntelliJ IDEA 安装

    • 安装

    从官网 下载
    对应安装包,然后将其解压;

    tar -zxvf ideaIU-2020.1.tar.gz 

    进入 IDEA/bin 目录下,执行 idea.sh 即可;

    • 生成快捷方式

    打开 IDEA 后,进入 Configure -> Create Desktop Entry,然后输入 root 用户密码即可;

    4.8 Pycharm 安装

    • 安装

    从官网 下载 对应安装包,然后将其解压;

    tar -zxvf pycharm-professional-2020.1.tar.gz 

    进入 Pycharm/bin 目录下,执行 pycharm.sh 即可;

    • 生成快捷方式

    打开 Pycharm 后,进入 Configure -> Create Desktop Entry,然后输入 root 用户密码即可;

    5. 系统美化

    5.1 Grub 美化

    • 对于安装双系统或多系统的读者,想要自己的启动页面变得好看一点,那么你一定不能错过。

    这里 去选择自己喜欢的主题,然后进行安装,这里笔者选择的是 Grub-theme-vimix

    • 安装

    把 Grub-theme-vimix 克隆到本地,然后使用如下命令进行安装即可;

    sudo ./install.sh -t -2 -b

    5.2 锁屏界面美化

    直接修改相关配置文件:/etc/alternatives/gdm3.css ,然后修改代码中的 #lockDialogGroup 部分如下即可;

    #lockDialogGroup {
      background: #2c001e       
      url(file:///home/cunyu/imgs/lock.png);
      background-size: cover;
      background-repeat: no-repeat;
      background-position: center;
    }

    5.3 美化工具安装

    美化之前,需要安装 gnome-tweak-tool,安装命令如下;

    sudo apt-get install gnome-tweak-tool

    5.4 主题安装

    • 下载

    推荐使用这套,macos 风格的主题,下载链接

    • 安装

    把下载好的压缩包解压之后,移动到 /usr/share/themes 目录下即可;

    5.5 图标安装

    • 下载

    推荐这款图标,下载链接

    • 安装

    把下载好的压缩包解压之后,移动到 /usr/share/icons 目录下即可;

    5.6 终端安装

    • 安装

    Ubuntu 自带终端已经很强大,但我更推荐使用 ZSH;

    在终端中使用如下命令安装即可;

    sudo apt install zsh

    • 设为默认 Shell
    chsh -s /usr/bin/zsh
    • 美化

    安装 oh-my-zsh,通过如下任一命令安装;

    # via crul
    sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    # via wget
    sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

    也可以通过如下命令安装;

    git clone git://github.com/robbyrussell/oh-my-zsh.git  ~/.oh-my-zsh
    cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

    安装好之后,对 zsh 进行配置,打开 ~/.zshrc 配置文件,进行配置;

    进入 主题列表,挑选自己喜欢的主题,然后在上述配置中改成你喜欢的主题,比如我的主题是 Powerlevel10k,使用如下命令进行下载,然后在 .zshrc 中配置 ZSH_THEME="powerlevel10k/powerlevel10k"

    git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/themes/powerlevel10k

    使用该主题时会乱码,所以需要安装字体,到字体下载链接选择喜欢的字体进行下载,然后解压后双击安装即可,然后到终端的设置中将字体设为刚才安装的字体之一,然后重启终端即可。

    • 插件安装

      • zsh-syntax-highlighting :语法高亮;
      git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting
    - `zsh-autosuggestions` :自动补全;
    
    git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
    # 主题
    ZSH_THEME="powerlevel10k/powerlevel10k"
    
    
    # 插件
    # git
    
    # zsh-syntax-highlighting 输入正确会绿色高亮显示,输入错误会显示其他的颜色
    # git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh
    
    # zsh-autosuggestions 自动建议补全
    git clone git://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh
    
    # extra 解压
    # z
    plugins=(
        git zsh-syntax-highlighting zsh-autosuggestions extract z
    )

    6. 总结

    至此,Ubuntu18.04 的安装结束,接下来你就可以尽情的捣鼓安装好的系统了 QAQ。

    20200510234310.png

    ]]>
    星辰大海:阿里数据体验技术揭秘!-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png
    在数字经济时代,数据的重要性堪比石油。大数据的四个特点:Volume(数据体量大)、Variety(数据类型繁多)、Velocity(处理速度快)、Value(商业价值高),只要合理利用数据并对其进行准确的分析,将会给企业带来很高的价值回报。

    image.png

    在数据建设侧,阿里巴巴数据体验技术团队完成了大型 Web 应用架构,TypeScript 方案的 Iron-Redux,加 API 服务 Pont。上层特色的服务编排引擎和 SQL 编辑器的深挖,在工具层体验能力增色不少。在数据消费侧,我们致力于构建以 BI 为搭建数据报表等核心能力,上层特色的数据可视化,数据艺术和数据安全都在各领域有深度贡献。

    SQL 编辑器

    在数据采集,加工,管理,应用的链路中,编辑器都充当着开发者最重要的伙伴。在前端领域,编辑器是高复杂度的领域,而 SQL 编辑器在这个领域中又是垂直领域的建设。我们在设计编辑器的过程针对理解语法和理解语境这两个点作了深入分析和优化。

    理解语法

    回顾人学习一门新语言的过程,无外乎两种方式,要么翻阅百科字典,从 a 到 z,无论常用字还是生僻字,可以组成哪些词语,悉数记于脑中;要么与人交谈,记下其他人遣词造句的习惯,转为己用,与人交谈的次数越多,交谈的人群范围越广,积累下的话术也越为丰富,面对同一个问题,心中可供选择的答案也越多。

    编辑器理解语法的过程亦是如此,基于产生式进行分析或是基于样本进行模型训练与识别。一门语言的产生式定义可以视为这门语言的百科字典,详尽的枚举出词法和语法的所有场景,清晰的描摹出边界。SQL 语言的语法相对简单,结构性较强就比较适合采用基于产生式分析的方案。而对于 Python,Shell 这类语法复杂,或灵活度高的语言,基于产生式分析提供的关键词提示,在实际应用中难以真正起到提高开发效率的目的,基于业务场景的历史数据进行模型训练,提供包含业务含义的代码片段的提示显得更有意义。

    image.png

    理解语法可以说是编辑器最为基础的能力,不同的编辑器方案均有不同程度的实现,在我们的方案设计与实现的过程中,最为关注的三个方面分别是可扩展,易维护,高性能三个方面。

    可扩展性

    编辑器的载体产品经常提出一些定制化的需求,譬如支持变量语法,注入全局变量,或者是调整提示内容优先级,新增一些快捷键操作。尽管最终效果的呈现都是在界面上,实际解决这些问题的原点却不尽相同。因此,对编辑器作了三层架构设计,产生式层,解析层,组件 UI 层,各层级间明确 API 规范来保证能力的解耦与扩展性。

    易维护

    在需求支持的过程中,我们无可避免的要对语言的产生式进行修改,在这个过程中任何思考上的疏忽都有可能引入二义性语法,或者左递归语法,导致解析异常,无法提示出正确的信息或是陷入死循环。于是,产生式调试是语法拓展的核心工作之一,我们需要追踪用户输入的解析过程,在哪一个子句处没有匹配到目标产生式,从而去检查相应的产生式定义是否存在问题。

    高性能

    高性能的保证和提示精度的调控,提示规则的设计是相辅相成的。总能找到一处语法定义是 LL(k) 识别不了,但是 LL(k+1) 可以识别的。性能的要求导致我们不能无限的提高回溯的步长,但是同时还要达到一定精度的要求。

    由于 SQL 本身流程控制能力上的局限,在实际开发中往往需要借助 transform 或 udf 的方案,结合 Python,Shell 一起开发。未来,我们也会用机器学习的方案,来支持 Python,Shell 的语法提示能力。

    理解语境

    为了做到理解语境,在充分理解语法的基础上,还要进一步对语法所在的上下文进行分析,才能得出最终的结论。语法解析的过程实际上就是将用户输入转化为抽象语法树的过程,因此理解语境所需的上下文尽在树中。但是在为编辑器赋予理解语境能力的过程中,我们发现最大的挑战并不在于分析逻辑的实现本身,而是在于它的复制量产。由于业务上要求支持的方言众多,方言彼此间有着或多或少的语法差异,既要保持不同方言间语境理解能力的齐平,又要避免冒烟囱式开发。经过分析我们发现针对每一种语境理解的实现,关注点都集中在几个关键的终结符与非终结符定义上,围绕这些关键节点,建立一个有限状态机,新的方言接入时提供一个映射关系来说明关键节点之间的映射,语境理解能力就完成复制量产。这套机制的建立也意味着编辑器能力开放的议题中,语境理解能力的开放已经初具雏形。

    理解语境的另一方面体现在与数据分析能力的结合。结合数据探查的能力,对于数据研发中一些常见的数据质量问题可以做到前置检测,在开发过程中予以提示并引导解决,从而改变传统数据治理方案流程滞后,导致计算资源浪费的问题。以数据倾斜治理为例,当用户任务执行失败,经过探查分析发现是由于数据倾斜所致,由于数据倾斜存在一个持续期,因此在持续期内,用户开发中再次操作引发倾斜的字段时,会将热值信息予以提示,引导用户进行改写。除去这个例子,其他类型的数据质量问题,诸如暴力扫描,join 两侧字段类型不一致,以及其他 map join 优化场景都可以通过开发时的检测与提示进行规避。

    640.gif
    语法提示

    640 (1).gif

    别名识别

    640 (2).gif

    关键词修正

    BI 平台

    BI 平台是帮助我们更好地利用数据提高决策质量的中后台应用架构。它是从大量数据前端应用中沉淀出来的,上层支撑了 Quick BI,Quick A+ 等数据产品,汇集并沉淀了数据分析的核心能力。

    image.png

    Quick BI 是 BI 平台支持的核心,也是数据中台拳头级产品。2019 年,成为中国首个且惟一入选 Gartner BI 魔力象限的产品。BI 是一个典型的重交互场景,整个 BI 的分析链路包括数据引入、数据建模加工、报表搭建、交互分析、访问分享和集成。

    image.png

    BI 是非常有挑战的搭建场景,搭建能力对阿里数据中台来说,就像水和空气一样重要,搭建能力很大程度决定了数据中台竞争力。大量的产品需要搭建的方式来赋能业务人员,类似 Powerpoint 一样富客户端的交互方式;动辄百万条数据的加载对性能的挑战,单页面项目、ISV 开放生态对接等对工程化充满挑战。因此,我们抽象出渲染引擎内核 bi-designer 同时支持了两个 BI 的快速迭代。

    目前,我们携 bi-designer 加入到阿里集团低代码引擎组织,成为核心架构组的一员,专注于数据领域搭建方向,正在积极的递交提案拓展阿里低代码引擎协议,并进行 bi-designer 规范化改造,与 AliLowCodeEngine 引擎进行代码融合。

    bi-designer 数据搭建引擎有如下几个特色:

    三合一布局

    我们三种业务场景天然需要三套布局的能力:

    • 数据搭建 - 流式布局
    • BI 报表 - 磁贴布局
    • 大屏 - 自由布局

    bi-designer 通过插件机制内置了这三种布局能力,使流式、磁贴、自由布局间可无缝切换,在不同的产品透出不同布局能力。自由布局采用 Keynote 高仿真方案,支持吸附、对齐、组合、锁定等功能;磁贴布局也做了高度自使用、宽高智能拖拽等体验优化。

    image.png

    数据模型驱动

    BI 搭建拥有强大运行时分析能力,比如基于 OLAP 数据模型的同数据集自动关联功能,在图表内点击、框选操作触发其他组件查询并自动过滤筛选。

    简单的联动配置背后需要一套运行时解析能力,bi-designer 提供了一套运行时事件机制 runtimeConfig ,将任意组件配置映射成复杂事件关联效果,比如点击控制图表属性变化、点击控制取数参数变化、点击控制筛选条件值的变化。在取数上也内置了数据模型处理,针对筛选条件多对多关联、联动、值同步,以及任意组件(如线图)具有展示与筛选双重功能的能力。

    image.png

    懒渲染

    由于 BI 场景数据量动辄上百万条,我们在做了大量组件优化的同时(虚拟滚动、数据抽样等),bi-designer 渲染引擎也内置了组件按需渲染的能力。

    对于数据取数场景,我们也做了特别优化,懒加载的组件不会阻塞取数,而只阻塞组件本身渲染,这样可以在组件出现的瞬间直接加载数据,而不是重新取数。

    image.png

    被集成能力

    BI 报表在阿里内部和云上市场都有强烈被集成诉求,若干张报表或者设计器集成到已有系统逐渐成为一种趋势,我们开发了 bi-open-embed 并集成 bi-designer 能力,让渲染引擎可以被轻松集成到任意平台。我们对 Client、Server 端抽象了三层通用中间层 - PostMessageProxy、Router、EmbedAPI,分别定义了消息派发、指令接收以及指令执行,Server 端底层对接 bi-designer 引擎,Client 端对接接入方应用。

    image.png

    被集成领域中,组件安全隔离我们也下了功夫,从组件打包阶段开始,就利用构建工具进行样式隔离,组件脚本加载时进行 JS 环境变量隔离。在集成 API 上我们也从权限体系开始设计,到报表区块、筛选、主题等模块的控制,还可支持动态传入取数参数影响报表取数结果等等。

    低代码

    低代码能力也是我们数据搭建平台今年重点建设方向之一。低代码包括 NoCode 与 LowCode,NoCode 就是基于模型或者标准化模版,不需要写代码即可支持通用场景搭建,LowCode 则是在 NoCode 的基础上,辅助少量业务逻辑代码,比如变量绑定或者或者动作,实现更多定制业务逻辑的覆盖,其适用范围更广。

    集团中后台搭建采用 LCDL(Low-Code Definition Language)描述低代码业务协议,主要包括应用低代码定义语言、页面低代码定义语言、组件低代码定义语言。围绕这三个核心协议,拓展出四个可插拔的核心模块:

    • 入料模块:让渲染引擎可接入任何物料,并识别物料特征,自动完成对接。
    • 编排模块:设计器核心功能,包括组件编排、逻辑编排、事件编排等,是搭建的核心。
    • 渲染模块:搭建产物需要被分发到不同端,甚至小程序,从安全性到兼容性都是渲染模块要解决的问题。
    • 出码模块:所有低代码搭建产物都可出码,使产物可二次开发,FY21 还会持续探索 Pro-Code 与 Low-Code 完全互转。

    image.png

    数据搭建场景较为垂直化,且可依赖数据模型驱动,所以很久以来都没有强烈低代码诉求。但随着数据中台业务越做越大,盒马、菜鸟、本地生活、饿了么等等经济体 BU 数据团队的加入,具有低代码能力的数据产品搭建诉求越来越强烈,所以我们今年加入了集团低代码引擎组织核心团队共建,向组织输出数据场景搭建能力,从组织获取低代码引擎能力。

    数据可视化

    人类在处理信息的能力,视觉远超其它五感处理能力,在《Information is Beautiful》一书中,作者将视觉类比成计算机网络的带宽,达到 1250MB/s,触觉排在第二类比成 USB 接口,有 125MB/s,听觉和嗅觉类比成硬盘只有 12.5MB/s,而味觉就忽略不计了。数据可视化,就是在我们清洗关键数据之后,将数据呈现给用户,就像一道菜品精美的摆放,给你带来更多想象。

    image.png

    可视化一直是数据分析重点一环,工业界和学术界在可视化的研究也有高度重合的部分,脱离了分析的可视化是缺少灵魂的。上图来自于一家调研公司,可以看到数据分析有一个发展阶段,描述分析,解释分析,探索分析,预测分析,规范分析。规范分析是数据分析的最终阶段,它已经可以解决我们怎么做的问题,类同于人工智能的终极阶段。

    而可视化在之前的几个阶段都有一一匹配的能力,这也是可视化与大部分认知不同的地方,可视化远远不只是解决数据如何展示的问题,更重要的是如何传递信息,甚至解决的是如何传递更多的有效信息。

    image.png

    基础架构

    我们团队在这四个阶段都有布局,在可视化图表上,也是我们立身之本。我们在 BI 工具发展上构建了整体数据团队可视化能力,基于 D3,G2 构建了完善的基础层。结合业务能力在工具层,拥有完善的交互式分析能力。通过上层图形语法和数据模型标准的映射,我们得以用一套架构来实现可视化内核,在内核层大量增强了基础库的能力。

    image.png

    BI 场景有极其丰富的可视化场景,目前 Quick BI 支持 40+ 种图表,包括用户最常用的折线图、柱状图、堆积面积图、条形图、饼图和桑基图、排行榜、指标卡趋势图这些业务场景化图表。

    如何让这些图表更快速的开发和扩展一直是我们要解决的问题。因此,我们抽象出了 charts-bi 图表公共层,它包括统一的数据处理层、图表默认配置管理、图表极限情况处理、子图表、子组件包括图例和 Tooltip 等原生的渲染。尽管,我们图表渲染底层基于 G2,但可以说我们的 BI 场景图表是 G2 在集团应用最丰富的场景之一,同时我们也深度参与了可视化基建的共建。

    经过一年多的努力,2019 Quick BI 在 Gartner 象限报告中在数据可视化上在满分 5 分的情况下达到了 4.7 高分,超越微软 PowerBI,仅次于以可视化著称的 Tableau。报告称 Quick BI 在数据可视化功能方面被评为杰出。它支持丰富的图表类型和类似 Excel 的报告。它还为参数化数据获取和基于表单的回写提供了专用功能。

    image.png

    智能可视化

    智能可视化是一个跨界领域,诸如自动洞察,可视化设计,可视化配置推荐都是智能可视化的方向。Gartner 预测,未来数据分析是增强分析时代。普通用户发现数据洞察的成本很高,用户需要尝试找到有规律的一些指标,通过可视化的方式来验证甚至是发现规律,这种反复试错的成本非常高。因此,自动洞察可以说是对用户在基本业务洞察之外的补充。

    自动洞察能够在用户提供的数据集上,自动进行特征的分析,数据清洗,根据梳理的 insight 类型进行自动的匹配,找到隐藏在数据集中的知识,提供给用户。这条链路的不断优化,最终手工分析的时代也会被自动洞察所替代,只有一个按键就可以让用户找到最有效的信息。

    近几年,产学研一体趋势显著,微软的 Power BI 在这个领域颇有建树,推出了 Quick Insight 就是与 MSRA 合作,是自动洞察方向的开荒者之一。今年,团队在对内侧的 BI 工具上已经落地了自动洞察功能,提供给小二更智能的数据决策工具而为之努力。

    在未来布局上,数据世界真实的描绘不应该是静态的,它是真实在变化的,我们在变化中需要呈现一个变化的规律,这是现代数据分析上比较缺失的。未来,在数据故事和自动洞察上发力。将数据可视化能力从静态交互分析,发展到动态交互分析上。进一步,挖掘数据中更多有效信息,辅助人作决策。

    数字艺术

    如果说 BI 和数据可视化是帮你从数据中发现信息的工具,数字艺术则是展示和传播你的数据故事的媒介。无论是一个大屏,还是一个可交互装置、Web 页面、富媒体,作为数据可视化的延伸,我们始终在探索更新颖的形式,更前瞻的技术,来让你的故事拥有怦然心动的力量,在从纷繁的信息中脱颖而出,无须多言,便能打动听众。

    团队与 UED 紧密合作,负责了集团多年的双十一和九号馆的大屏和互动展区设计开发,支持了集团 PR、GR 部门众多的高级接待和对外访问活动,同时以技术支持和平台支持的形式,协助集团各个BU团队开发数据可视化、BI、大屏、展示型内容和传播内容。

    image.png

    在这个过程中,积累了一套从数据处理算法、到渲染引擎、可视化框架,以及搭建平台的解决方案。

    异构渲染

    为了满足不同场景和内容的展示需求,充分发挥不同渲染平台的优势,我们在渲染框架中原生支持了跨平台渲染。

    通过一套统一的数据源/数据算法,统一的地理空间规范,和统一的相机状态定义,来保证不同渲染架构输出的渲染结果空间对齐,然后通过实时推流和 WebView 对不同渲染架构的内容进行融合,实现多种平台渲染内容的合并输出。

    通过异构渲染,我们得以在一个场景中,融合来自 Polaris 的实时数据可视化能力、UE4 的细节制作和渲染能力、G2 丰富的可视化图表、高德庞大的地图数据,各取所长,融合为一个可交互的实时渲染内容。

    image.png

    Web3D 渲染引擎

    无论技术流行趋势如何变化,Web 平台依然承载着我们和核心技术和产品形式。我们始终相信,Web 平台有着未被发掘的强大表现力。受限于设备性能和兼容性的妥协,大家已经习惯将 3D on Web 视为一种能用即可的“降级方案”,然而 Web 技术的应用领域已经发生了巨大的变化,基础设施和硬件设备也不断的更新换代,现代 Web3D 渲染引擎,不应该总向十年前的游戏引擎看齐。

    我们的渲染技术始终以 three.js 为根基,作为标杆级的 WebGL 引擎,three 无可匹及的强大社区支持着我们的快速发展,当我们试图摆脱 webgl 的限制时,选择保留了 three 所有的上层设计和场景定义,兼容多数 three 接口和社区插件的情况下,重写渲染层,实现了(当时)功能最完整的 WebGL2 引擎,显著提升性能效果,实现在浏览器中实时渲染上亿顶点的大型场景。同时引入了高效的 GPGPU,来实现更加复杂的粒子动画。

    image.png

    从 WebGL2 到未成行的 WebGPU ,限制 Web 3D 渲染能力的已经不再是图形 API,而是渲染流水线和图形算法,渲染管线不升级,图形 API 再怎么升级也不会带来质的变化。

    在 2019 年,我们作出了一个大胆的尝试,直接将桌面游戏引擎的高清渲染管线,实现在 WebGL2 之上,并在双十一项目中使用。

    image.png

    参照成熟游戏引擎的渲染管线设计,我们使用多种渲染路径来渲染场景的不同成分,对不能完整支持 Deferred Shading 的材质,或者需要兼容的现有入库组件,使用前置渲染或者不完整的延迟渲染,其中基础场景使用完整的Deferred Shading 管线。Deferred Shading 管线中,除了获得在 shading 阶段强大的性能控制,完整的 GBuffer 更是解锁了众多屏幕空间算法,让我们可以高效的进行 SSAO 和 SSR 等复杂计算,甚至能在笔记本上流畅运行。

    image.png

    高清渲染管线为 Web 端的渲染能力迈上了一个新的平台,使得众多新技术的引入成为可能,我们能从学术、游戏、影视领域的最新成果中汲取养分,而不用再受限于实时渲染技术发展初期的古老算法。这也将是唯一一个有能力发挥 未来 WebGPU 潜力的渲染管线。

    3D 地理系统

    3D 数据可视化的场景虽然复杂而零散,但是多数和地理数据强相关,为了提高开发效率和服用能力,我们在 three 的通用场景定义之上,设计了一个轻量级的地理信息系统 — Polaris。将视觉效果的开发和业务逻辑的开发,全部简化为 Layer 开发,符合 schema 的 Layer 可以自由叠加、组合、继承,然后交由 Polaris 渲染器渲染。同时积累了一套上百种 3D 组件的 Layer 组建库。

    image.png

    image.png

    在 Polaris 中,我们按照真实比例和地理位置构建世界,可以向 Google Earth 一样,在一个三维场景中绘制从星系到地球、国家、一直到某个城市的某个楼宇内部,通过一个长镜头,将所有的场景贯穿起来,形成一个连贯的数据故事,而非 PPT 一般的分离图表。

    image.png

    image.png

    算法服务

    一个 3D 空间的构建,是对大量原始地理数据的层层处理,处理的过程往往从高德的原始地理数据开始,经过过滤、简化、拔高,变为3维模型,然后通过算法进行细化、匹配,生成建筑细节,然后根据 DEM 生成地形和山脉,对于特写区域,需要通过人工制作高精模型,再通过算法对齐地理空间,如果是城市景观,还可以通过算法生成车水马龙、广告牌、霓虹灯等装饰内容。

    所有这些过程都涉及到庞大的数据输入和复杂的计算逻辑,我们已经在不同业务场景中生成过 30 多个城市和所有省份的场景数据,并且数据源和场景构建的需求在不停的更新着,手工管理将是一笔巨大的负担。因此我们构建了一个地理数据算法服务,来为 UE4 和 Web 端渲染平台提供等效的服务。

    image.png

    数据安全

    数据产品中有表现力丰富的数据可视化能力、有高效的数据解读能力,但背后也隐藏着各种各样的数据安全风险,这些数据可能涉及到交易数据,也可能是行业趋势数据,这些数据如果发生泄漏被不法分子利用极有可能对集团和客户造成严重的损失,所以数据产品的各个生产环节都需要具备安全防御方案。

    对于整个数据安全体系,从数据生产到数据查询的链路需要做数据脱敏和差分隐私,前端是数据产品向用户展示和交互的最后一环,也是整个生产链路中数据安全保障的最后一环。我们也需要配合全链路的数据安全建设,做到事前,事中和事后的保障。

    image.png

    数字水印

    数字水印是在事前环节,起到警示、震慑的作用,提醒产品的访问者产品中展现的数据属于秘密信息,不可传播;以及在事后环境,帮助案件追查,当访问者对页面进行截图和传播后,可以准确的定位到案件的当事人,协助案件侦破。传统的网页水印方案是在端上直接绘制水印信息覆盖在文档流上或作为背景图片,但只要是具备一点前端知识的人都可以通过浏览器的调试器对水印信息进行删除甚至是篡改,所以我们要确保水印的真实性和水印的稳固性。

    水印生产主要分两类。明文水印,它是指将可读的信息(用户姓名或其他警示信息)生成水印图片,主要目的是起到警示作用;加密水印,我们团队和安全专家一同共建的,将水印信息加密为不同形式的图片方案,比较有代表性的是结合了文件数字水印的理论:点阵水印、隐秘水印、浮雕水印,此类水印的特点是信息无法被篡改,主要用于案件追踪。

    image.png

    水印在生产环节进行了加密,由于无法解密,解决了水印的篡改问题。在水印部署过程中需要克服水印被删除的问题,我们针对常见的水印删除手段进行防御:

    1)网络干预,阻止页面获取水印资源(图片和 SDK),对图片二进制流和sdk资源下发均采用与业务代码进行混淆,确保水印部署和业务逻辑执行过程交叉执行,无法单独中断。

    2)环境及 DOM 干预,通过修改和删除 DOM,对浏览器的 DOM 和关键原生 API进行劫持验证,确保 DOM 的修改可被监听和复原,无法复原的情况将中断业务逻辑。

    3)融合部署:对于重要的数据信息,通过 canvas 或图片方式渲染替代原有的DOM渲染,此时数据信息和水印信息融合为一个原子节点,无法单独删除。

    4)信息差手段:每个数据产品的页面均混合部署多套水印方案,通过虚实结合(可见水印、不可见水印)的方式确保不法分子无法完全删除掉页面中的水印。

    除了以上攻防手段,我们还在进行一些新型水印生产及部署方向的研究,目的是从更本质的方面对水印进行加固同时降低水印对产品的视觉侵入,目前具备可行性的是边缘水印(将水印的生产和部署在客户端进行,强化水印和页面内容的融合)和血缘水印。

    数据监控

    监控在数据产品中不仅是生产安全的重要部分,同时也是数据安全的重要保障,通常的监控主要关注 JS 报错和 API 接口报错,但数据产品需要对数据逻辑进行监控,例如用户正在分析自己商业数据,突然实时数据显示利润剧烈下跌,这很容易造成用户的恐慌。

    image.png

    我们在完成了监控平台 Clue 的基础建设上同时也在做很多数据场景的垂直建设:

    **非法环境识别
    **
    因为有数据透出,总是有黑灰产不断生长。他们通过第三方插件爬取数据接口,对数据进行非法汇总和反推,这时就需要对用户使用产品的浏览器环境进行检查,为打击黑产提供法律依据。针对这种场景我们会对部分原生 API 进行数字指纹加持,通过特征匹配,定位非法插件。

    诊断报警

    监控平台中报警的及时性和准确性是重中之重。对于数据展现来说,某些数据趋势跌 0 对于大客户和来说一定是出现了问题,但对一些小众客户或行业来说,部分指标的跌 0 可能是正常表现。因此,我们需要抽取指标信息、行业信息、大促信息等在内的多种特征对报警模型优化,通过报警信息的反馈机制不断的对报警模型持续优化,通过模型预测的方式对报警阈值进行动态调整,促使报警逐渐趋于准确。

    写在最后

    未来,数据与智能占据了风口。依靠着云上计算能力,提供强大的渲染与数据分析能力。在流程研发,分析洞察,科学决策等方面都是体验技术发挥至关重要力量之处。倚靠业界和学界前沿方向探索,数据体验技术围绕数据应用的全生命周期进行建设和打造,数据的未来是星辰大海,望有志之士一同前来创造未来!

    你99%的储存实力,只差这1%的简历投递——数据技术及产品部【数据平台技术与设计-体验技术团队】招人进行中,期待与你一起用创意与代码为世界做出些许改变。邮箱 arcthur.cheny@alibaba-inc.com

    ]]>
    【其他】8月31日ECS云服务器优惠调整通知 Fri, 20 Jun 2025 02:20:33 +0800

    尊敬的阿里云用户:

    您好阿里云中国站(www.aliyun.com)ECS部分产品将于2020年8月31日开始进行优惠活动折扣调整,明细如下:

    1.第四代企业级实例(包含计算网络增强型,通用网络增强型,内存网络增强型)将于2020年8月31日23:59结束原专有网络运营推广优惠活动,自2020年9月1日0:00起专有网络下此类产品优惠活动调整为购买或续费享受包1年8.5折,2年7折,3年5.5折,4年4.5折,5年3.8折。

    2.第五代企业级实例(包含密集计算型,计算型,通用型,内存型)将于2020年9月30日23:59结束原专有网络运营推广优惠活动,自2020年10月1日0:00起专有网络下此类产品优惠活动调整为购买或续费享受包1年8.5折,2年7折,3年5.5折,4年4.5折,5年3.8折。

    调整的地域包含除中东东部1外的所有中国站地域。请您提前规划好资源使用需求,如有需要,可提前续费或更换更新的6代或6代增强型实例,以获得更好的业务性能。

    (调整后折扣详情请参看如下表格)

    ]]>
    领域驱动设计详解:是什么、为什么、怎么做? Fri, 20 Jun 2025 02:20:33 +0800 image.png

    一 什么是领域驱动设计

    领域驱动设计的概念是2004年Evic Evans在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计:软件核心复杂性应对之道)中提出的,从领域驱动设计提出距今已经有15年的时间,为什么最近才开始在中国的互联网圈大行其道?似乎一夜之间大家都在谈论,那么领域驱动设计到底帮我们解决了什么问题?带着这些疑问,一起来看下阿里巴巴文娱是如何实践领域驱动设计的。

    二 领域驱动设计大行其道的必然原因

    软件系统从来都不是凭空而来,而是以软件的形式解决特定的问题。当我们面临现实世界的复杂问题时,如何以软件的形式落地?领域驱动设计是一套方法论,指导我们将复杂问题进行拆分、拆分出各个子系统间的关联以及是如何运转的,帮助我们解决大型的复杂系统在落地中遇到的问题。

    Evic Evans在著作中将软件系统的设计分为2个部分:战略设计和战术设计。在战略设计层面提出了域、子域、限界上下文等重要概念;在战术设计层面提出了实体、值对象、领域服务、领域事件、聚合、工厂、资源库等重要概念。如图1所示:

    image.png

    战略设计部分指导我们如何拆分一个复杂的系统,战术部分指导我们对于拆分出来的单个子系统如何进行落地,在落地过程中应该遵循哪些原则。

    以大家熟知的电子商务系统举例,早期的电商系统因为业务相对简单,用户量和团队规模也较小,一个单体应用就可以搞定,随着容量上升可以将单体应用进行横向扩容,比如早期的淘宝就是这样做的。拆分过程中我们可以把电商系统这个单体应用拆分成订单子系统、库存子系统、物流子系统、搜索推荐子系统等等,如图2所示:

    image.png

    领域驱动设计在战略层面上的域、子域、限界上下文的划分思想和微服务的划分不谋而合。域对应一个问题空间,也就是上例中的电商系统;子域是把域这个大的问题空间拆分成若干个小的更容易解决的问题空间,也就是单体应用向微服务演进过程中划分出来的各个子系统;限界上下文是解决方案空间,每个子域对应一个或多个解决方案空间。微服务的划分是也是将一个大的问题拆分成若干个小的问题,每一个小的问题用一个或多个微服务来解决。

    对于大多数开发同学来说都没有机会接触系统的划分,这些工作一般是公司的技术领导层与架构师来做的,普通的开发同学日常工作中接触到的只是某一个具体微服务或微服务中某一个模块的落地,那是不是说领域驱动设计对于普通开发同学来说就没有用了?当然不是这样,领域驱动设计中的战术设计部分就是指导我们如何落地一个系统才可以使系统具备高可扩展性、高可读性。

    所有的系统最终都要以代码的形式落地,而落地的工作都是由普通的开发同学来做的,系统是否具备高可扩展性、高可读性直接影响了整个团队的效率。

    三 传统分层架构存在的问题

    对于大多数开发同学来说,大部分时间都花在落地一个个微服务上,下面我们来看阿里文娱是如何结合领域驱动设计的思想将微服务进行战术落地的。

    目前笔者接触过的微服务大多数都是分层架构并且在Service层与Manager层实现具体的业务逻辑,使用DO、DTO、BO、VO等进行数据传输,数据和行为基本完全隔离。这种分层结构图3是《Java开发手册》中的标准分层结构。

    该规范中定义了各层的职责,其中最重要的两层Service层和Manager层是这样规范的(以下两层解释摘抄自《Java 开发手册》):

    Service层

    相对具体的业务逻辑服务层。

    Manager层

    通用业务处理层,它有如下特征:

    • 对第三方平台封装的层,预处理返回结果及转化异常信息。
    • 对Service层通用能力的下沉,如缓存方案、中间件通用处理。
    • 与DAO层交互,对多个DAO的组合复用。

    image.png

    阿里文娱早期的项目分层也基本都采用这种架构形式。上面的分层并没有问题,但是这种分层架构采用的是包的形式进行的层与层的隔离,需要每一位开发同学理解并且自觉遵守以上规范,但是在实际工作中我们发现很多同学对Service层和Manager层的区别并不是特别的清楚,即使清楚的同学大部分也并没有完全遵守手册中的规范,这种现象导致Manager层除了沉底一些通用能力以外和Service层并没有什么本质区别。

    在实际的业务代码中Service层和Manager层都充斥了大量的第三方依赖,对系统的稳定性有很大的影响。每依赖一个第三方服务都要各种异常问题,这些异常处理的代码往往会和业务代码混在一起,当这种代码多了以后会使代码的可读性非常差。

    阿里文娱业务的复杂度提升很快,业务迭代速度也很快, Service层和Manager层代码量迅速膨胀,业务逻辑变得越来越复杂。在这种业务场景下,大文娱引入了领域驱动设计并设计了一套完整的领域驱动模型评估与演进的解决方案来辅助开发同学将领域驱动设计的思想真正的落地。

    四 文娱领域驱动设计实践

    领域驱动设计的关键在于识别业务的模型,而模型又是会随着业务的发展而演进的,对于新的业务来说能效平台提供了业务模型分析的功能,开发同学可以在能效平台设计并搭建自己的领域模型,搭建出来后能效平台可以评估领域模型设计的是否合理,如果模型设计合理则可以基于以上设计的模型符合领域模型规范的代码。对于已有应用,能效平台设计了一套领域注解并以SDK的形式提供出去:

    • 第一步:开发同学按照领域设计的原则对业务代码进行分析并打上注解。
    • 第二步:能效平台可自动扫描该项目并收集该项目中的领域模型。
    • 第三步:模型收集后,开发同学可以在能效平台改进业务模型并重新按照领域模型的规范生成代码。

    完整流程如下图所示:

    image.png

    1 模型采集

    对于已有的准备重构的应用,我们设计了一套领域模型的注解,开发同学可以将注解加到对应的类、属性、方法上。当系统是按数据模型落地而不是按领域模型的方式落地时,可以先找到系统的数据模型,然后在能效平台对数据模型进行组织生成领域模型。

    image.png

    2 模型搭建

    对于新应用或者已经进行完模型采集的应用,开发同学可以在能效平台进行模型的搭建和修改,如图6所示。

    image.png

    3 健康度评估

    对于已经搭建完的模型能效平台,根据领域驱动设计的规范创建了一套完整的校验规则,模型搭建完成在生成脚手架之前会根据校验规则进行打分,当打分通过时可以将模型生成脚手架。

    image.png

    4 脚手架生成

    当模型搭建完毕并且校验通过后可以将模型生成脚手架,其代码结构是按照六边形架构的标准生成的,六边形架构也成为端口与适配器架构,该架构的思想是将内部核心的领域逻辑与外界依赖进行隔离,这里的依赖是指所有对其他微服务的依赖、http的依赖、数据库依赖、缓存依赖、消息中间件依赖等等,所有的这些依赖都通过适配器进行转换成应用可理解可识别的最小化信息。

    在实际的项目中,每种依赖都要考虑各种异常情况并进行处理,而这些处理实际上并不数据领域逻辑,却耦合到了业务代码里,当这种依赖多了对系统的稳定性会产生很大的影响,传统的分层架构虽然也会让我们将自身的领域逻辑和依赖进行分离,在阿里巴巴规范手册中提到所有的依赖都应该放到Manager层,但是这种规范是非常容易被打破的。六边形架构从应用分层上让我们更容易去遵守这样的规范。

    image.png

    根据六边形架构的指导思想,在实际的应用分层中一般划分为四层,分别是:

    • 用户接口层:负责用户展现相关的逻辑。
    • 应用层:负责对一个用例进行流程编排(将接口用例分成若干个步骤,但是不负责每步的具体实施)。
    • 领域层:负责实现核心的领域逻辑即业务逻辑(负责实现具体的业务逻辑)。
    • 基础设施层:所有依赖的具体实现。

    但是从应用架构的角度看,层级组织形式可以分为两种:

    传统分层架构

    如图9左侧,这种分层架构是Evic Evans在《Domain-Driven Design : Tackling Complexity in the Heart of Software》中提出的,其中用户接口层、应用层、领域层可直接依赖基础设施层,与图3的传统架构并无本质区别,因为所有层都直接依赖了基础设施层。这种方式需要强制开发同学将所有的依赖进行下沉,随着时间的推移这种规范非常容易被打破。

    image.png

    依赖倒置的分层架构

    如图9右侧,这种分层架构是依赖倒置的分层架构,特点是:

    • 基础设计层可直接依赖其他三层,反之则不行。
    • 用户接口层、应用层、领域层如果要使用基础设施层中的能力,只能通过IOC的方式进行依赖注入,这也遵从了面向对象编程中的依赖倒置原则。

    当开发同学要在以上三层中直接引用第三方依赖时,是找不到具体的类信息的,也就是不能import。同时这种方式对单元测试的规范也可以起到很大的作用,当我们编写单元测试时可以为领域层注入一个测试运行时的依赖,这样应用运行单元测试可以不依赖下游服务,在代码层面上也更加规范。

    五 总结

    经典的三层或多层架构虽然是目前最普遍的架构,但是在隔离方面做得并不够好。在业务架构选型时要结合自身业务特点,而不能千篇一律的选择某一种业务架构,合适的业务架构可以延长项目的生命周期,降低项目的重构频率,最终达到降低人力成本的目的。

    ]]>
    阿里产品专家:高情商的技术人,如何做沟通? Fri, 20 Jun 2025 02:20:33 +0800 1.png

    作者 | 磊之

    不愿沟通是固执,不会沟通是傻瓜,不敢沟通是奴隶。——德拉蒙德

    工作中,你是否经常看到别人在会上谈笑风生、纵横捭阖,但自己却唯唯诺诺,不敢表达观点?即便鼓起勇气发言却不被重视,经常被人打断?生活中,你提出个很好的家庭规划,却没人支持你?规劝自己的亲友却被误会,最后以吵架收场?

    当你的朋友指出你的问题可能在“沟通”能力上时,你却轻蔑一笑:“沟通这么简单事情,我会不懂?”

    要知道许多大师花了一辈子研究“沟通”,最终觉得自己只能驾驭一些类型的沟通。

    互联网时代的信息媒介很发达,但非常碎片化,你可能听过很多道理,但未必有意识地组织过,人脑对于没有体系化的观点,总会选择性遗忘。

    所以,关于沟通,你可能需要重新思考。

    2.png

    真理的世界如同泥泽地一般充满了陷阱。树立一个清晰的“概念”好比在这片泥泽地里打桩,树立一个“理论”好比在这些木桩上架桥。

    PART 1:沟通的定义?

    3.png

    街头的大妈们家长里短地唠嗑,能不能叫“沟通”?大学课堂上教授口若悬河,滔滔不绝地讲课,能不能叫沟通?

    沟通是“有目的的多向信息交流”。大妈聊天漫无目的,最多是“多向信息交流”,不是“沟通”;教授讲解虽有目的,但如果没有与学生互动,则也不算是“沟通”。

    “沟通的方式一定是谈话么”?不是,能交流信息即可,不拘泥于形式。沟通完全可以借助文字、图片、音乐......比如写信、发邮件,甚至眼神交流也算,宋词中“执手相看泪眼,竟无语凝噎”,这里的“执手相看泪眼”,就是“为了表达感情的双向信息交流”,也是一种沟通。

    之所以一定要死板地界定沟通的意义,是因为这太重要了。在沟通过程中要时刻有意识地提醒自己:

    • 沟通是有目的的:你的初衷是什么?你现在的行为和言语有没有妨碍你完成你的初衷?
    • 沟通是多向信息交流:你有让对方get到你的真实想法么?你get到对方的真实想法了么?

    一次成功的沟通,有什么样的特点呢?

    4.png

    从情绪体验和信息匹配的维度上,可以把沟通划分成四个区域: 

    • 安慰区:安慰区的情绪体验好,但信息一致度不高。这可能是沟通的启动状态,或者沟通表面和谐,但大家互相打哈哈,没在深层次上达成共识;
    • 目标区:这是期望达到的状态;
    • 破坏区:非常糟糕的情形,没达成任何共识,且场面十分不和谐:争吵激烈,或者干脆沉默、冷暴力;
    • 风险区:共识达成,但人际关系变差的情形。虽然达成了共识但最终因团队无法齐心协作而难以执行。

    看看自己的沟通在哪个区域分布最多,就可以给自己的沟通能力打分了。

    PART 2:沟通的作用

    5.png

    人类是社会动物,人与人、人与群体通过“沟通”传递信息和情感。沟通的作用有如下几点:

    • 获取或传递信息和知识
    • 促成一致观点或协议
    • 情感管理

    这种分类并不符合“mece”法则,而是互有交叉的,也即一次沟通的作用可能有多条。

    Eg:你跟一个女孩一起喝咖啡,碰巧被你女朋友知道了。你准备找她沟通,修复关系。在这次沟通中,你要获取信息(她看到了什么?她现在什么想法?),你也要向女友传递信息(女孩是谁?为什么与你约会?),之后你要情感管理(表达你对女友的真心,并期望得到他的谅解),最后你们也可能达成一些“共同观点和协议”(共同观点:与女孩喝咖啡是为了聊工作,没有劈腿。或者共同协议:以后跟其他女生单独约会要报备......)。

    PART 3:沟通的方法

    6.png

    这里提供两个方法论,第一个“周哈里窗”模型。第二个“螺旋推进”模型。

    •  “周哈里窗”模型侧重强调信息的共享的重要性,并用图示的方法清晰的表达这种倾向;
    • “螺旋推进”模型侧重沟通的流程,是一个完备的沟通框架。

    7.png

    “周哈里窗”根据信息是否被“我”知道,以及是否被“对方”知道,分成四个区域:

    • 公众区:我知道且对方也知道的信息

    比如上述例子中信息“我与女孩喝咖啡”,这个信息我知道,我女朋友也知道,属于“公众区”。

    • 私区:我知道的但对方不知道的信息

    上述例子中信息“女孩是我同事,我正与她交接工作”,则可看成是隐私区的信息。

    • 盲点区:我不知道但对方知道的信息

    上述例子中假如“女友看到了非常生气,联想到我这个星期微信聊天对她有点冷淡,非常不开心”,那么这个信息女友知道,我不知道,属于“盲点区”的信息。

    • 黑洞区:我跟对方都不知道的信息

    假如“喝咖啡的女孩一直暗恋我”。这个信息我与女朋友都不知道,则属于“黑洞区”。 ok,我们界定清楚了基本概念,那么这个“周哈里窗”到底有啥用呢?

    周哈里窗模型认为沟通的关键是要“拓展公众区的信息”。比如,事情发生了,男生一定要把隐私区的信息“女孩是我同事,我正与她交接工作”向女友传达清楚,并使她相信,而女生也要把盲点区的信息“看到了非常生气,联想到我这个星期微信聊天对她有点冷淡,非常不开心”这个信息传递给我,这样我们才可能消除误会。说来简单,但之后的情节可能是这样的: 

    1. 我没解释清这个女孩只是与我交接工作,女友大发雷霆......
    2. 女友没有告诉她生气还因为“这个星期微信聊天我对她有点冷淡”,那么我即便解释清楚喝咖啡的事情也是徒劳的......

    哎,坑好多。光靠这个窗还不行啊。因为周哈里窗只告诉你要做到什么,但没告诉你具体怎么做到啊。 一个可以指导我们沟通流程的框架——“螺旋推进”模型,是时候出场了。

    8.png

    是不是有点疑惑?

    • “安全氛围”是什么,咱大老爷们还提“安全”,丢不丢人?
    • “监控器”指的难道是要我装个摄像探头?
    • “共识线”、“情感线”,是两根什么“线”?

     “安全氛围”,指的是要让整个沟通过程中你与对方都拥有“安全感”,这是沟通的前提条件。为此你要表现出对对方足够的尊重,并让对方觉得你是一个值得信任的人,当你开始沟通时,你会有意识提醒自己注意氛围,但一旦聊到正题部分,你就很难时刻保持对“安全氛围”的感知和把控。

    比如,因为你一个持续 0.5 秒的“不屑的眼神”,对方瞬间感到:“你刚才表现出来对我的尊重都是装的,你内心看不起我。”他很可能失去了“安全感”,进入“戒备状态”......

    所以,“时刻保持对你们沟通的监控”非常重要。怎么形成这种意识呢?这里就要说到神奇的“监控器”了。

    9.png

    “监控器”不是要你真装一个摄像头,这么说完全是为了帮助理解。试着想象一下,如果有一个非常牛逼的监控器在全程监控你们的沟通,你们的沟通遇到问题进行不下去时,你可以随时暂停沟通,通过监控器看到从开始沟通到当下你与对方沟通的所有情形(包括语言、表情和动作),相信你一定会有新的思路:

    • 当你觉得对方冥顽不灵而感到十分愤怒,且马上要发作的时候,监控器“BBBB”发出声响,从监控器中你看到一个气急败坏,马上要爆发的自己,你突然意识到:“沟通是有目的的,场面一旦失控,就 game over了”。
    • 当对方已经多次回避你的问题,顾左右而言他时,监控器继续“BBBB”发出声响,从监控器中你看到对方“眼神闪烁,目光游离,欲言又止的情形”时,你意识到刚才才讲的沟通要“多向信息交流”,他显然没敞开心扉交流啊,我应该先想办法让他放下戒备,说出自己的真实想法,才能推动成功的小船驶向前方啊。

    当然,监控器是一个只存在你脑海里的假想物,它给了你一个上帝视角,让你客观、全局地审视“你们的这次沟通”,沟通问题的端倪出现时,你能第一时间发现,而不是等到“场面失控”“对方拒绝沟通”的情况发生时才意识到。 “共识线”“情感线”是存在于沟通过程中的两条推进线路,成功的沟通必然让这两条线路共存,无论是“共识线”还是“情感线”发生问题时,都要用“螺旋推进”的方式,解决问题,而不是二愣子一样拼命往前冲。

    基本概念理清楚了,那么“螺旋推进”模型具体步骤是什么呢?

    10.png

    我们解读一下,“螺旋推进”共有5个步骤:构建安全氛围、构建共同目标、分析当前问题、交流解决方案、最后协议。

    1. 构建安全氛围

    如前文所言,安全氛围是沟通的前提,沟通前先要确保双方心平气和,愿意敞开心扉。如果你有问题在先,对方很可能处于消极对抗的状态,此时不要着急跟对方解释,也不要埋怨对方,而是先让 ta 相信:你很在意 ta 的想法,这次沟通你是带着十分的诚意来的,不会敷衍塞责。待 ta 心绪平静下来时,再往下推进。

    2. 构建共同目标

    这时要让 ta 觉得“你们有共同的目标,沟通成功对双方都有利”。以下列举了一些雷区:

    雷区 1:越过第二步,直接进入第三步分析问题,这会让对方对你的动机产生怀疑,分析问题时,他就会有所保留,并在过程中不断试探你的真实动机。如果从“周哈里窗”模型的角度看,此时你无法获取“盲点区”的信息(对方知道而你不知道的信息),“公众区”无法更多地被拓展,沟通缺少质量。

    雷区 2:让 ta 觉得你是“只为达成自己的目的而来”,当遇到“利益冲突”的时候,这很致命。

    比方部门里讨论来年的战略规划,你与同事阿力在方案上争执不下,场面一度十分僵硬(部门采用谁的方案,对你俩之后在部门的权威影响很大)。这种情形下你该如何与 ta 沟通?ta 跟你一样都觉得自己方案很棒,应该被采纳。争执如果处理不好,不但影响你们之间的关系,而且之后不管部门选择了谁的方案,相信另一方肯定不会用心支持,甚至阳奉阴违,对部门来说,这是巨大损失。

    此时如果你能从你俩的共同目标“给客户带来最大价值,帮助团队获得成功”出发来沟通,问题会简化很多,此时你们站在了一条船上,这条船能够驶向目标对大家都有帮助。所以你可以试着这样说:

    “阿力啊,我觉得你跟我的方案都有一定道理,虽然我们现在还在争议哪个方案更好,但我们的目标都是更好地帮助部门,我们与其争执不下,不如先回去好好想想自己和对方的方案,获取更多的数据证明自己的方案能给客户带来更多价值,最后如果还是无法定夺,则让老大拍板。但无论老大最后选谁的方案,我们都坚定地帮助执行,你觉得这样处理如何?”相信这么说,他对你会有更多的认同。 为什么会画“构建共同目标”到“构建安全氛围”的回旋线呢?

    这是因为当比较激烈的冲突发生时,你在推进“构建共同目标”的过程中可能因措辞不当,或者对方没感受到你的诚意等等原因,导致对话失去了“安全氛围”,此时你要回退一步,该道歉道歉,该解释解释,重新“构建安全氛围”,而一味地向前蛮推,必败无疑。

    11.png

    3. 分析当前问题

    分析当前问题和交流解决方案是这次沟通的主要内容。在问题和问题的原因没有完全浮出水面时,不要着急讨论方案。原因有是:

    • 你们可能看到的是表面问题,没看到本质问题;
    • 你们可能看到了部分问题,没看到全部问题;
    • 问题的原因是多样的,与问题一样需要找到本质原因与全部原因。

    解决方案是针对问题来的,之前提到的例子中,刚开始你了解的问题只是“女朋友误会我跟其他女孩约会”,而不知道其他问题“女友觉得你这周微信回复非常冷淡”,所以即便你解释清楚了“女孩只是与你交接工作”后,女友还是闷闷不乐,于是你们开始争吵,直到场面失控.......

    我们再回顾一下沟通的定义,“有目的的多向信息交流”。信息交流是表达与倾听构成的,倾听的目的是为了理解信息。所以“不要光听ta说了什么,还要考虑ta怎么说的”(语气、语调、表情、动作这里传递的信息可能比语言本身更多),推测ta的真实想法,当你发现ta并未坦露心扉时(问题解释了,依然消极,精神不济)则需要引导ta表达,你可以这样说:“我刚刚观察到你的表情依然显得不太高兴,你觉得这件事情上我没有解释清楚还是因为其他事情不开心,可以聊聊么?”有时对方的状态非常消极,一次引导没有效果时,你可以换个方式继续,直到她愿意分享为止。

    12.png

    4. 交流解决方案

    交流解决方案时,无论谁提出解决方案,都要带着“这是我的建议,我希望得到你的反馈,以便设计出更符合我们共同目标的方案”的心态。永远不要陷入“二选一”陷阱里,要坚信“第三种方案”能让大家都满意,在第 4 步上你可能也有一些误区:

    • 误区一:只要对方满意就好,我可以委屈自己

    这是典型的老好人心态。“吃亏是福”并不总是对的,“助人为乐”也未必要牺牲自己。如用经济学“社会总福利”概念来理解:ta 快乐 1 分,你痛苦 1 分,你俩的总快乐还是 0 分,并没提升啊。你完全可以表达出自己的合理需求,比方在俩人的出行计划上想想有没有第三种方案,同时能符合你俩的需求,实在不行,也可以妥协,在一件事上你听 ta 的,在另一件事上,ta 听你的。

    反过来如果你一味委屈自己,你觉得你能在过程中一点也不表现你可能并不满意这次出行么?人是很难持续伪装自己情感的,到时 ta 看到你的不开心,ta 也会失去兴致,与其这样,为啥不一开始就定一个“让大家都开心的方案”呢?

    • 误区二:不愿/不敢表达观点

    这个在多人会议里很常见,一些非常“有意思”的同事,他们或许个人能力不错,但在跨团队、跨部门的大会上,总是一声不吭,会上定出的方案后来效果不好时,他们就出来做事后诸葛亮,各种吐槽“我早知道这么做不好”。此刻 ta 以为别人会这么想:“这个人好牛b啊,几个部门作出的方案一眼就看出问题,应该让 ta 来 carry 啊。”然而实际上别人会这么想:“不知道会上是不愿说还是不敢说,不愿说证明 ta 自私,不敢说证明 ta 性格软。” 

    5. 达成最后协议

    恭喜你,前面的“九九八十一难”被你攻克了,拿到最后的结果就是水道渠成了。

    达成协议后你要充分尊重这个“最后的协议”,并带头执行,这样可以建立良好的信任关系,下次沟通别的什么事情时,你会在“构建安全氛围”和“构建共同目标”上会花费更少的时间。 最后总结一下全文的要点:

    • 关于沟通,你可能需要重新思考;
    • 沟通的定义是:“有目的的多向信息交流”。沟通的时候要时刻提醒自己这一点;
    • 沟通的作用:“获取或传递信息和知识”、“促成一致观点或协议”、“情感管理”;
    • 沟通的方法论:“周哈里窗”与“螺旋推进”模型;
    • “周哈里窗”模型:分为公众区、隐私区、盲点区、黑洞区。沟通的关键在于拓展“公众区”的信息;
    • “螺旋推进”模型:概念(监控器、安全氛围管理、共识线、情感线);5 个步骤(构建安全氛围、构建共同目标、分析问题、交流解决方案、达成最后的协议);
    • 践行最后的协议可以帮助建立信任,下次沟通更有默契。

    参考资料:《关键对话》、《哈佛人力资源管理》

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    正确入门Service Mesh:起源、发展和现状 Fri, 20 Jun 2025 02:20:33 +0800 image.png

    引言

    随着云原生时代的来临,微服务架构与容器化部署模式越来越流行,从原来的新潮词汇慢慢演变成为现代IT企业的技术标配。曾经被认为理所当然的巨无霸单体应用,被拥抱了微服务的架构师们精心拆分成了一个又一个小而独立的微服务,接着再被拥抱了容器化的工程师们打包成了自带依赖的Docker镜像,最后通过某种神秘的DevOps流水线持续运送到前线 —— 也就是无人不知的 —— 风暴降生·谷歌之子·打碎镣铐者·云时代操作系统·Kubernetes —— 之中部署和运行。

    听上去似乎一切都很美好?显然不是,这世上永远没有免费的午餐。所有美好的东西都会有它的阴暗面,微服务也不例外:

    • 原来只需要部署和管理单个应用,现在一下裂变成了好几个,运维管理成本成倍上升。
    • 原来各个模块之间的交互可以直接走应用内调用(进程间通信),现在都给拆分到了不同进程甚至节点上,只能使用复杂的RPC通讯。

    难道辛辛苦苦落地了微服务,只能一边在老板面前强撑着“没问题,一切安好”,另一边默默忍受着研发与运维的私下抱怨?显然也不是。对于以“偷懒”著称的程序员们,办法总是比困难多。比如上面第1个问题,云原生所倡导的DevOps和容器化,就是一剂几乎完美的解药:通过自动化的CI/CD流水线,多应用的集成构建部署变得更加快捷;通过Docker镜像和K8s编排,多应用的资源调度运维管理也变得不那么痛苦。至于第2个问题,那就该看本文的主角 —— Service Mesh(服务网格),是如何力挽狂澜,近乎完美地解决微服务之间的通讯问题了。

    什么是 Service Mesh?

    Service Mesh 诞生

    从概念到落地?不,是从落地到概念。

    image.png

    时间回到2016年9⽉29⽇,那是一个即将放假迎来普天同庆的日子(是说我们)。在Buoyant公司内部一次关于微服务的分享会上,“Service Mesh” ,这个接下来几年占据各种云原生头条的 buzz word,就这么被造出来了。不得不说,起名真是门艺术,Micro-Services -> Service Mesh,多么承前启后和顺其自然啊,光看名字就能很形象地理解这玩意儿所做的事情:把微服务的各个service(服务)节点,用一张mesh(网格)连接起来。就这样,原本被拆散得七零八落的微服务们,又被 Service Mesh 这张大网紧密得连接到了一起;即使依然天各一方(进程间隔离),但也找回了当年一起挤在单体应用内抱团撒欢的亲密感(通信更容易)。

    最难得的是,这么好的一个概念居然不是从PPT里走出来的,人家是真的有货(这让广大PPT创业者们情何以堪):2016年1⽉15⽇,Service Mesh的第一个实现Linkerd [1]就已经完成了初次发布,紧接着次年1月23日 加入了CNCF,同年4月25日发布了 1.0版本。对于Buoyant公司而言,这也许只是无心插柳的一小步,但却是云原生领域迈向成熟的一大步。几年后的今天,Service Mesh 概念早已深入人心,各种生产级实现和大规模实践也已遍地开花,但请不要忘记这一切背后的功臣、Service Mesh革命先驱、Buoyant公司CEO —— William Morgan,以及他对Service Mesh的定义和思考:What's a service mesh? And why do I need one?[2]

    Service Mesh 定义

    别废话了,我没工夫听你说这么多,请用一句话跟我解释 Service Mesh 是什么。

    好的。A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.

    这就是上面那位又帅又能写的CEO,对Service Mesh的权威定义。我把其中一些重点词汇做了高亮:

    • “dedicated infrastructure layer”:Service Mesh 不是用来解决业务领域问题的,而是一层专门的基础设施(中间件)。
    • “service-to-service communication”:Service Mesh 的定位很简单也很清晰,就是用来处理服务与服务之间的通讯。
    • “reliable delivery of requests”:服务间通讯为什么需要特殊处理?因为网络是不可靠的,Service Mesh 的愿景就是让服务间的请求传递变得可靠。
    • “cloud native application”:Service Mesh 从一开始就是为现代化的云原生应用而生,瞄准了未来的技术发展趋势。
    • “network proxies”:具体 Service Mesh 应该怎么实现?典型方式都是通过一组轻量级的网络代理,在应用无感知的情况下偷偷就把这事给干了。
    • “deployed alongside application code”:这些网络代理一定是跟应用部署在一起,一对一近距离贴心服务(比房产中介专一得多);否则,如果应用与代理之间也还是远程不靠谱通讯,这事儿就没完了。

    Service Mesh 形态

    想致富,先修路;但大马路可不是给马走的,是给更现代化的汽车。

    image.png

    左边这张图是Linkerd的部署示意图,其中每个微服务所处的主机(host)或容器组(pod)中都会部署一个Linkerd代理软件,用于代理微服务应用实例之间的RPC调用。对于应用而言,这一切都是无感知的:它还是照常发起自己的RPC调用,只是不再需要关心对端服务方的地址,因为服务发现都由代理节点给cover了。

    右边是一张更高维和抽象的大图,可以更形象地理解 Service Mesh 的逻辑形态 —— 想象这就是一个生产级的大规模微服务集群,其中部署了上百个服务实例以及对应的 Service Mesh 代理节点;所有微服务之间的通讯都会流经这些密密麻麻的代理节点,它们共同组成了一张川流不息的现代化交通网格。

    为什么需要 Service Mesh ?

    微服务的崛起

    The Big Bang:大爆炸后的混乱之治。

    image.png

    大多数人都曾经历过那个单体应用为王的时代。所谓“单体”(Monolithic),就是把所有组件都塞在同一个应用内,因此这些组件天然就紧密联系在一起:基于相同技术栈开发、访问共享的数据库、共同部署运维和扩容。同时,这些组件之间的通讯也趋向于频繁和耦合 —— 不过就是一句函数调用的事,何乐而不为。这样做本身并没什么错,毕竟那时的软件系统相对简单,可能一个人写个两万行代码的单体应用,就能轻松搞定所有业务场景。

    天下大事,分久必合,合久必分。现代化软件系统的复杂度不断提升,协作人数也越来越多,单体应用的固有局限性开始暴露。就仿佛宇宙大爆炸前的那个奇点,单体应用开始加速膨胀,最终在几年前达到了临界点,然后“砰”的一声就炸开了。就这样,微服务时代王者降临,让软件开发重新变得“小而美”:

    • 单⼀职责:拆分后的单个微服务,通常只负责单个高内聚自闭环功能,因此很易于开发、理解和维护。
    • 架构灵活:不同微服务应用之间在技术选型层面几乎是独立的,可以⾃由选择最适合的技术栈。
    • 部署隔离:相比巨无霸单体应用,单个微服务应用的代码和产物体积大大减少,更容易持续集成和快速部署;同时,通过进程级别的隔离,也不再像单体应用一样只能同生共死,故障隔离效果显著提升。
    • 独⽴扩展:单体应用时代,某个模块如果存在资源瓶颈(e.g. CPU/内存),只能跟随整个应用一起扩容,白白浪费很多资源。微服务化后,扩展的粒度细化到了微服务级别,可以更精确地按需独立扩展。

    但显然,微服务也不是银弹。大爆炸虽然打破了单体应用的独裁统治,但那一声声炸裂之后的微服务新宇宙,显然也不会立即就尘埃落定,而是需要经历很长一段时间的混乱之治。适应了单体时代的开发者们,被迫需要拥抱微服务所带来的一系列变化。其中最大的变化,就是服务间通讯:

    如何找到服务的提供⽅?

    微服务通讯必须走远程过程调用(HTTP/REST本质上也属于RPC),当其中一个应用需要消费另一个应用的服务时,无法再像单体应用一样通过简单的进程内机制(e.g. Spring的依赖注入)就能获取到服务实例;你甚至都不知道有没有这个服务方。

    如何保证远程调⽤的可靠性?

    既然是RPC,那必然要走IP网络,而我们都知道网络(相比计算和存储)是软件世界里最不可靠的东西。虽然有TCP这种可靠传输协议,但频繁丢包、交换机故障甚至电缆被挖断也常有发生;即使网络是好的,如果对方机器宕机了,或者进程负载过高不响应呢?

    如何降低服务调⽤的延迟?

    网络不只是不可靠,还有延迟的问题。虽然相同系统内的微服务应用通常都部署在一起,同机房内调用延迟很小;但对于较复杂的业务链路,很可能一次业务访问就会包括数十次RPC调用,累积起来的延迟就很可观了。

    如何保证服务调⽤的安全性?

    网络不只是不可靠和有延迟,还是不安全的。互联网时代,你永远不知道屏幕对面坐的是人还是狗;同样,微服务间通讯时,如果直接走裸的通讯协议,你也永远不知道对端是否真的就是自己人,或者传输的机密信息是否有被中间人偷听。

    服务通讯:石器时代

    毛主席说:自己动手,丰衣足食。

    image.png

    为了解决上述微服务引入的问题,最早那批吃螃蟹的工程师们,开始了各自的造轮子之旅:

    • 服务发现(Service Discovery):解决“我想调用你,如何找到你”的问题。
    • 服务熔断(Circuit Breaker):缓解服务之间依赖的不可靠问题。
    • 负载均衡(Load Balancing):通过均匀分配流量,让请求处理更加及时。
    • 安全通讯:包括协议加密(TLS)、身份认证(证书/签名)、访问鉴权(RBAC)等。

    用自己的代码解决问题,这确实是程序员们能干出来的事,没毛病。But,时间都去哪了?

    • 重复造轮子:需要编写和维护⼤量非功能性代码,如何集中精力专注业务创新?
    • 与业务耦合:服务通讯逻辑与业务代码逻辑混在一起,动不动还会遇到点匪夷所思的分布式bug。

    服务通讯:摩登时代

    社会主义精神:共享和复用。

    image.png

    更有思想觉悟的那批工程师们坐不住了:你们这是违背了共享和复用原则,对不起GNU那帮祖师爷!于是,各种高质量、标准化、期望能大一统的精品轮子们应运而生,包括 Apache Dubbo(手动置顶)、Spring Cloud、Netflix OSS、gRPC 等等等。

    这些可复用的类库和框架,确确实实带来了质量和效率上的大幅提升,但是足够好使了吗?Not enough:

    • 并非完全透明:程序员们仍然需要正确理解和使⽤这些库,上手成本和出错概率依然很高。
    • 限制技术选择:使用这些技术后,应用很容易就会被对应的语⾔和框架强绑定(vendor-lock)。
    • 维护成本高:库版本升级,需要牵连应⽤一起重新构建和部署;麻烦不说,还要祈祷别出故障。

    服务通讯:新⽣代

    Service Mesh:我只是一个搬运工而已。

    image.png

    Service Mesh 的诞生,彻底解决了上述所有问题。听上去很神奇,究竟是如何办到的呢?简单来说,Service Mesh 就是通过 Sidecar模式[3] ,将上述类库和框架要干的事情从应用中彻底剥离了出来,并统一下沉到了基础设施层。这是一种什么思想?这是一种古老操作系统中早就有了的抽象和分层思想(应用程序并不需要关心网络协议栈),也是一种现代云计算平台自底向上逐步托管的软件服务化思想(IaaS -> CaaS -> PaaS -> SaaS)。

    上述几张 Service Mesh 的演进图,参考自 Service Mesh Pattern[4] 一文。

    Service Mesh 主流实现

    注:以下内容来自于资料搜集整理,仅供参考,更进一步学习请以最新权威资料为准。

    主流实现概览

    image.png

    Service Mesh 的主流实现包括:

    • Linkerd:背后公司是Buoyant,开发语⾔使用Scala,2016年1⽉15日初次发布,2017年1⽉23日加入CNCF,2018年5⽉1⽇发布1.4.0版本。
    • Envoy:背后公司是Lyft,开发语言使用C++ 11,2016年9月13日初次发布,2017年9⽉14日加⼊CNCF,2018年3月21日发布1.6.0版本。
    • Istio:背后公司是Google和IBM,开发语言使用Go,2017年5⽉月10日初次发布,2018年3⽉31日发布0.7.1版本。
    • Conduit:背后公司也是Buoyant,开发语言使用Rust和Go,2017年12月5日初次发布,2018年4⽉27日发布0.4.1版本。

    Linkerd 简介

    image.png

    Linkerd的核心组件就是一个服务代理,因此只要理清它的请求处理流程,就掌握了它的核心逻辑:

    • 动态路由:根据上游服务请求参数,确定下游目标服务;除了常规的服务路由策略,Linkerd还可以通过这一层动态路由能力,支持灰度发布、A/B测试、环境隔离等非常有价值的场景。
    • 服务发现:确定目标服务后,下一步就是获取对应的实例的地址列表(e.g. 查询service registry)。
    • 负载均衡:如果列表中有多个地址,Linkerd会通过负载均衡算法(e.g. Least Loaded、Peak EWMA)选择其中⼀个合适的低延迟实例。
    • 执行请求:发送请求到上一步所选择的实例,并记录延迟和响应结果。
    • 重试处理:如果请求未响应,则选择另⼀个实例重试(前提:Linkerd知道该请求是幂等的)。
    • 熔断处理:如果发往某个实例的请求经常失败,则主动从地址列表中剔除该实例。
    • 超时处理:如果请求超期(在给定的deadline时间点之前仍未返回),则主动返回失败响应。
    • 可观测性:Linkerd会持续收集和上报上述各种行为数据,包括Metrics和Tracing。

    Envoy 简介

    image.png

    Envoy是一个高性能的Service Mesh软件,主要包含如下特性:

    • 高性能:基于本地代码(C++ 11)实现;相比之下,Linkerd是基于Scala编写,肯定要慢不少。
    • 可扩展:L4和L7层代理功能均基于可插拔的 Filter Chain 机制(类比 netfilter、servlet filter)。
    • 协议升级:支持双向、透明的 HTTP/1 to HTTP/2 代理能力。
    • 其他能力:服务发现(符合最终一致性)、负载均衡(支持区域感知)、稳定性(重试、超时、熔断、限速、异常检测)、可观测性(统计/日志/追踪)、易于调试等。

    Istio 简介

    image.png

    Istio是一个管控/数据平面分离的完整Service Mesh套件,包含如下组件:

    • Envoy:构成数据平⾯(其他组件共同构成控制平⾯);可被替换为其他代理(e.g. Linkerd, nginMesh)。
    • Pilot:负责流量管理(Traffic Management),提供平台独⽴的服务模型定义、API以及实现。
    • Mixer:负责策略与控制(Policies & Controls),核⼼功能包括:前置检查、配额管理、遥测报告。
    • Istio-Auth:支持多种粒度的RBAC权限控制;支持双向SSL认证,包括身份识别、通讯安全、秘钥管理。

    Istio 组件 - Pilot

    image.png

    Pilot组件是Istio服务网格中的“领航员”,负责管理数据平面的流量规则和服务发现。一个典型的应用场景就是灰度发布(or 金丝雀发布、蓝绿部署):开发者通过Pilot提供的规则API,下发流量路由规则到数据平面的Envoy代理,从而实现精准的多版本流量分配(e.g. 将1%的流量分配到新版本服务)。

    Istio 组件 - Mixer

    image.png

    Mixer组件是Istio服务网格中的“调音师”,既负责落实各种流量策略(如访问控制、限速),也负责对流量进行观测分析(如日志、监控、追踪)。这些能力都是通过前文提到的Envoy Filter Chain扩展机制实现:Mixer会分别在“请求路由前”(Pre-routing)扩展点和“请求路由后”(Post-routing)扩展点挂载自己的Filter实现。

    Istio 组件 - Auth

    image.png

    Auth组件是Istio服务网格中的“安全员”,负责处理服务节点之间通信的认证(Authentification)和鉴权(Authorization)问题。对于认证,Auth支持服务之间的双向SSL认证,可以让通讯的双方都彼此认可对方的身份;对于鉴权,Auth支持流行的RBAC鉴权模型,可以实现便捷和细粒度的“用户-角色-权限”多级访问控制。

    Conduit 简介

    image.png

    Conduit是由Buoyant公司出品的下一代 Service Mesh。作为Istio的挑战者,Conduit的整体架构与Istio类似也明确区分了管控平面和数据平面,但同时它还具备如下关键特性:

    • 轻量快速:Conduit的数据平面是基于原生的Rust语言编写,非常轻量、快速和安全(Rust相比C/C++的最大改进点就是安全性)。单个代理的实际内存消耗(RSS)小于10mb,延迟的p99分位点小于1ms,基本相当于能为应用程序提供免费(无额外开销)的Service Mesh功能。
    • 安全保障:Conduit构建之初就考虑了云原生环境的安全性,包括Rust语言内存安全性、默认TLS加密等。
    • 端到端可见性:Conduit可以自动测量和聚合服务的成功率、延迟与请求容量数据,让开发者在无需变更应用代码就能获取到服务的完整行为视图。
    • Kubernetes增强:Conduit为K8s集群添加了可靠性、可见性和安全性,同时给予了开发者对自己应用程序运行时行为的完全控制。

    结语

    本文从云原生时代所面临的微服务通讯问题入手,依次介绍了Service Mesh的起源、发展和现状,希望能帮助读者建立一个初步的理解和认知。当然,实践出真知,与其临渊羡鱼(眼馋Service Mesh的技术红利),不如退而结网(自己动手织一张Service网格)。手头的工作没有可实践的业务场景?没关系,我这有:

    欢迎各位技术同路人加入阿里云云原生应用研发平台EMAS团队,我们专注于广泛的云原生技术(Backend as a Service、Serverless、DevOps、低代码平台等),致力于为企业、开发者提供一站式的应用研发管理服务,内推直达邮箱:pengqun.pq # alibaba-inc.com,有信必回。

    相关链接

    [1]https://github.com/linkerd/linkerd
    [2]https://buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/
    [3]https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar
    [4]https://philcalcado.com/2017/08/03/pattern_service_mesh.html

    ]]>
    Dubbo大纲 Fri, 20 Jun 2025 02:20:33 +0800

    ]]>
    Spring事务源码分析专题(一)JdbcTemplate使用及源码分析 Fri, 20 Jun 2025 02:20:33 +0800 # 前言

    本系列文章为事务专栏分析文章,整个事务分析专题将按下面这张图完成

    image-20200718220712800

    对源码分析前,我希望先介绍一下Spring中数据访问的相关内容,然后层层递进到事物的源码分析,主要分为两个部分

    1. JdbcTemplate使用及源码分析
    2. Mybatis的基本使用及Spring对Mybatis的整合

    本文将要介绍的是第一点。

    JdbcTemplate使用示例

    public class DmzService {
    
        private JdbcTemplate jdbcTemplate;
    
        public void setDataSource(DataSource dataSource) {
            jdbcTemplate = new JdbcTemplate(dataSource);
        }
    
        /**
         * 查询
         * @param id 根据id查询
         * @return 对应idd的user对象
         */
        public User getUserById(int id) {
            return jdbcTemplate
                    .queryForObject("select * from `user` where id  =  ?", new RowMapper<User>() {
                        @Override
                        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                            User user = new User();
                            user.setId(rs.getInt("id"));
                            user.setAge(rs.getInt("age"));
                            user.setName(rs.getString("name"));
                            return user;
                        }
                    }, id);
        }
    
        public int saveUser(User user){
            return jdbcTemplate.update("insert into user values(?,?,?)",
                    new Object[]{user.getId(),user.getName(),user.getAge()});
        }
    }
    public class Main {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("tx.xml");
            DmzService dmzService = cc.getBean(DmzService.class);
            User userById = dmzService.getUserById(1);
            System.out.println("查询的数据为:" + userById);
            userById.setId(userById.getId() + 1);
            int i = dmzService.saveUser(userById);
            System.out.println("插入了" + i + "条数据");
        }
    }

    数据库中目前只有一条数据:

    image-20200708153438245

    配置文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
            <property name="password" value="123"/>
            <property name="username" value="root"/>
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url"
                      value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
    
        </bean>
    
        <bean id="dmzService" class="com.dmz.spring.tx.service.DmzService">
            <property name="dataSource" ref="dataSource"/>
         </bean>
    </beans>

    程序允许结果:

    查询的数据为:User{id=1, name='dmz', age=18}
    插入了1条数据

    image-20200708153656393

    运行后数据库中确实插入了一条数据

    对于JdbcTemplate的简单使用,建议大家还是要有一定熟悉,虽然我现在在项目中不会直接使用JdbcTemplate的API。本文关于使用不做过多介绍,主要目的是分析它底层的源码

    JdbcTemplate源码分析

    我们直接以其queryForObject方法为入口,对应源码如下:

    queryForObject方法分析

    public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
       
        // 核心在这个query方法中
        List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
       
        // 这个方法很简单,就是返回结果集中的数据
        // 如果少于1条或者多余1条都报错
        return DataAccessUtils.nullableSingleResult(results);
    }

    query方法分析

    // 第一步,对传入的参数进行封装,将参数封装成ArgumentPreparedStatementSetter
    public <T> T query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
        return query(sql, newArgPreparedStatementSetter(args), rse);
    }
    
    // 第二步:对sql语句进行封装,将sql语句封装成SimplePreparedStatementCreator
    public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
        return query(new SimplePreparedStatementCreator(sql), pss, rse);
    }
    
    
    public <T> T query(
        PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
        throws DataAccessException {
        // query方法在完成对参数及sql语句的封装后,直接调用了execute方法
        // execute方法是jdbcTemplate的基本API,不管是查询、更新还是保存
        // 最终都会进入到这个方法中
        return execute(psc, new PreparedStatementCallback<T>() {
            @Override
            @Nullable
            public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
                ResultSet rs = null;
                try {
                    if (pss != null) {
                        pss.setValues(ps);
                    }
                    rs = ps.executeQuery();
                    return rse.extractData(rs);
                }
                finally {
                    JdbcUtils.closeResultSet(rs);
                    if (pss instanceof ParameterDisposer) {
                        ((ParameterDisposer) pss).cleanupParameters();
                    }
                }
            }
        });
    }

    execute方法分析

    // execute方法封装了一次数据库访问的基本操作
    // 例如:获取连接,释放连接等
    // 其定制化操作是通过传入的PreparedStatementCallback参数来实现的
    public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
        throws DataAccessException {
        // 1.获取数据库连接
        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        PreparedStatement ps = null;
        try {
            // 2.获取一个PreparedStatement,并应用用户设定的参数
            ps = psc.createPreparedStatement(con);
            applyStatementSettings(ps);
            // 3.执行sql并返回结果
            T result = action.doInPreparedStatement(ps);
            // 4.处理警告
            handleWarnings(ps);
            return result;
        }
        catch (SQLException ex) {
              // 出现异常的话,需要关闭数据库连接
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer) psc).cleanupParameters();
            }
            String sql = getSql(psc);
            psc = null;
            JdbcUtils.closeStatement(ps);
            ps = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("PreparedStatementCallback", sql, ex);
        }
        finally {
            // 关闭资源
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer) psc).cleanupParameters();
            }
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }

    1、获取数据库连接

    对应源码如下:

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        // 这里省略了异常处理
        // 直接调用了doGetConnection方法
        return doGetConnection(dataSource);    
      
    }

    doGetConnection方法是最终获取连接的方法

    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
    
        // 如果使用了事务管理器来对事务进行管理(申明式事务跟编程式事务都依赖于事务管理器)
        // 那么在开启事务时,Spring会提前绑定一个数据库连接到当前线程中
        // 这里做的就是从当前线程中获取对应的连接池中的连接
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            // 记录当前这个连接被使用的次数,每次调用+1
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }
            return conHolder.getConnection();
        }
        Connection con = fetchConnection(dataSource);
    
        // 如果开启了一个空事务(例如事务的传播级别设置为SUPPORTS时,就会开启一个空事务)
        // 会激活同步,那么在这里需要将连接绑定到当前线程
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            try {
                ConnectionHolder holderToUse = conHolder;
                if (holderToUse == null) {
                    holderToUse = new ConnectionHolder(con);
                }
                else {
                    holderToUse.setConnection(con);
                }
                // 当前连接被使用的次数+1(能进入到这个方法,说明这个连接是刚刚从连接池中获取到)
                // 当释放资源时,只有被使用的次数归为0时才放回到连接池中
                holderToUse.requested();
                TransactionSynchronizationManager.registerSynchronization(
                    new ConnectionSynchronization(holderToUse, dataSource));
                holderToUse.setSynchronizedWithTransaction(true);
                if (holderToUse != conHolder) {
                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                }
            }
            catch (RuntimeException ex) {
                // 出现异常时释放连接,如果开启了事务,不会真正调用close方法关闭连接
                // 而是把当前连接的使用数-1
                releaseConnection(con, dataSource);
                throw ex;
            }
        }
    
        return con;
    }

    2、应用用户设定的参数

    protected void applyStatementSettings(Statement stmt) throws SQLException {
        int fetchSize = getFetchSize();
        if (fetchSize != -1) {
            stmt.setFetchSize(fetchSize);
        }
        int maxRows = getMaxRows();
        if (maxRows != -1) {
            stmt.setMaxRows(maxRows);
        }
        DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
    }

    从上面代码可以看出,主要设立了两个参数

    1. fetchSize:该参数的设计目的主要是为了减少网络交互,当访问ResultSet的时候,如果它每次只从服务器读取一条数据,则会产生大量的开销,setFetchSize的含义在于,当调用rs.next时,它可以直接从内存中获取而不需要网络交互,提高了效率。这个设置可能会被某些JDBC驱动忽略,而且设置过大会造成内存上升
    2. setMaxRows,是将此Statement生成的所有ResultSet的最大返回行数限定为指定数,作用类似于limit。

    3、执行Sql

    没啥好说的,底层其实就是调用了jdbc的一系列API

    4、处理警告

    也没啥好说的,处理Statement中的警告信息

    protected void handleWarnings(Statement stmt) throws SQLException {
        if (isIgnoreWarnings()) {
            if (logger.isDebugEnabled()) {
                SQLWarning warningToLog = stmt.getWarnings();
                while (warningToLog != null) {
                    logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
                                 warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
                    warningToLog = warningToLog.getNextWarning();
                }
            }
        }
        else {
            handleWarnings(stmt.getWarnings());
        }
    }

    5、关闭资源

    最终会调用到DataSourceUtilsdoReleaseConnection方法,源码如下:

    public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
        if (con == null) {
            return;
        }
        if (dataSource != null) {
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            if (conHolder != null && connectionEquals(conHolder, con)) {
                // 说明开启了事务,那么不会调用close方法,之后将连接的占用数减1
                conHolder.released();
                return;
            }
        }
        // 调用close方法关闭连接
        doCloseConnection(con, dataSource);
    }

    总结

    总的来说,这篇文章涉及到的内容都是比较简单的,通过这篇文章是希望让大家对Spring中的数据访问有一定了解,相当于热身吧,后面的文章难度会加大,下篇文章我们将介绍更高级的数据访问,myBatis的使用以及基本原理、事务管理以及它跟Spring的整合原理。

    如果本文对你由帮助的话,记得点个赞吧!也欢迎关注我的公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。

    我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

    ]]>
    蚂蚁集团网络通信框架 SOFABolt 功能介绍及协议框架解析 | 开源 Fri, 20 Jun 2025 02:20:33 +0800

    ,有趣实用的分布式架构频道。
    回顾视频以及 PPT 查看地址见文末。欢迎加入直播互动钉钉群 : 30315793,不错过每场直播。

    SOFAChannel#17

    大家好,我是本期 SOFAChannel 的分享讲师丞一,来自蚂蚁集团,是 SOFABolt 的开源负责人。今天我们来聊一下蚂蚁集团开源的网络通信框架 SOFABolt 的框架解析以及功能介绍。本期分享将从以下四个方面展开:

    • SOFABolt 简介;
    • 基础通信能力解析;
    • 协议框架解析;
    • 私有协议实现解析;

    SOFABolt 是什么

    SOFABolt 产生背景

    相信大家都知道 SOFAStack,SOFAStack(Scalable Open Financial Architecture Stack)是一套用于快速构建金融级云原生架构的中间件,也是在金融场景里锤炼出来的最佳实践。

    SOFABolt 则是 SOFAStack 中的网络通信框架,是一个基于 Netty 最佳实践的轻量、易用、高性能、易扩展的通信框架,他的名字 Bolt 取自迪士尼动画《闪电狗》。他一开始是怎么在蚂蚁集团内部产生的,我们可以类比一下 Netty 的产生原因:

    • 为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO 的实现以及处理难以调试的网络问题,Netty 应运而生;
    • 为了让中间件开发者能将更多的精力放在产品功能特性实现上,而不是重复地一遍遍制造通信框架的轮子,SOFABolt 应运而生;

    这些年,在微服务与消息中间件在网络通信上,蚂蚁集团解决过很多问题、积累了很多经验并持续进行着优化和完善,我们把总结的解决方案沉淀到 SOFABolt 这个基础组件里并反馈到开源社区,希望能够让更多使用网络通信的场景受益。目前该组件已经运用在了蚂蚁集团中间件的微服务 (SOFARPC)、消息中心、分布式事务、分布式开关、以及配置中心等众多产品上。

    同时,已有数家企业在生产环境中使用了 SOFABolt,感谢大家的肯定,也希望 SOFABolt 可以给更多的企业带来实践价值。

    SOFABolt 企业用户

    以上企业信息根据企业用户 Github 上反馈统计 — 截止 2020.06。

    SOFABolt:https://github.com/sofastack/sofa-bolt

    SOFABolt 框架组成

    SOFABolt 整体可以分为三个部分:

    • 基础通信能力(基于 Netty 高效的网络 IO 与线程模型、连接管理、超时控制);
    • 协议框架(命令与命令处理器、编解码处理器);
    • 私有协议实现(私有 RPC 通信协议的实现);

    下面,我们分别介绍一下 SOFABolt 每个部分的具体能力。

    基础通信能力

    基础通信模型

    基础通信模型

    如上图所示,SOFABolt 有多种通信模型,分别为:oneway、sync、future、callback。下面,我们介绍一下每个通信模型以及他们的使用场景。

    • oneway:不关注结果,即客户端发起调用后不关注服务端返回的结果,适用于发起调用的一方不需要拿到请求的处理结果,或者说请求或处理结果可以丢失的场景;
    • sync:同步调用,调用线程会被阻塞,直到拿到响应结果或者超时,是最常用的方式,适用于发起调用方需要同步等待响应的场景;
    • future:异步调用,调用线程不会被阻塞,通过 future 获取调用结果时才会被阻塞,适用于需要并发调用的场景,比如调用多个服务端并等待所有结果返回后执行特定逻辑的场景;
    • callback:异步调用,调用线程不会被阻塞,调用结果在 callback 线程中被处理,适用于高并发要求的场景;

    oneway 调用的场景非常明确,当调用方不需要拿到调用结果的时候就可以使用这种模式,但是当需要处理调用结果的时候,选择使用同步的 sync 还是使用异步的 future 和 callback?都是异步调用,又如何在 future、callback 两种模式中选择?

    显然同步能做的事情异步也能做,但是异步调用会涉及到线程上下文的切换、异步线程池的设置等等,较为复杂。如果你的场景比较简单,比如整个流程就一个调用并处理结果,那么建议使用同步的方式处理;如果整个过程需要分几个步骤执行,可以拆分不同的步骤异步执行,给耗时的操作分配更多的资源来提升系统整体的吞吐。

    在 future 和 callback 的选择中,callback 是更彻底的异步调用,future 适用于需要协调多个异步调用的场景。比如需要调用多个服务,并且根据多个服务端响应结果执行逻辑时,可以采用 future 的模式给多个服务发送请求,在统一对所有的 future 进行处理完成协同操作。

    超时控制机制

    在上一部分的通信模型中,除了 oneway 之后,其他三种(sync、future、callback)都需要进行超时控制,因为用户需要在预期的时间内拿到结果。超时控制简单来说就是在用户发起调用后,在预期的时间内如果没有拿到服务端响应的结果,那么这次调用就超时了,需要让用户感知到超时,避免一直阻塞调用线程或者 callback 永远得不到执行。

    在通信框架中,超时控制必须要满足高效、准确的要求,因为通信框架是分布式系统的基础组件,一旦通信框架出现性能问题,那么上层系统的性能显然是无法提升的。超时控制的准确性也非常重要,比如用户预期一次调用最多只能执行3秒,因为超时控制不准确导致用户调用时线程被阻塞了4秒,这显然是不能接受的。

    超时控制

    SOFABolt 的超时控制采用了 Netty 中的 HashedWheelTimer,其原理如上图。假设一次 tick 表示100毫秒,那么上面的时间轮 tick 一轮表示800毫秒,如果需要在300毫秒后触发超时,那么这个超时任务会被放到'2'的 bucket 中,等到 tick 到'2'时则被触发。如果一个超时任务需要在900毫秒后触发,那么它会被放到如'0'的 bucket 中,并标记 task 的 remainingRounds=1,当第一次 tick 到'0'时发现 remainingRounds 不等于0,会对 remainingRounds 进行减1操作,当第二次 tick 到'0',发现这个任务的 remainingRounds 是0,则触发这个任务。

    如果将时间轮的一次 tick 设置为1秒,ticksPerWheel 设置为60,那么就是现实时钟的秒针,走完一圈代表一分钟。如果一个任务需要再1分15秒后执行,就是标记为秒针走一轮之后指向第15格时触发。关于时间轮的原理推荐阅读下面这篇论文:
    《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》。

    快速失败机制

    超时控制机制可以保证客户端的调用在一个预期时间之后一定会拿到一个响应,无论这个响应是由服务端返回的真实响应,还是触发了超时。如果因为某些原因导致客户端的调用超时了,而服务端在超时之后实际将响应结果返回给客户端了会怎么样?

    这个响应结果在客户端会被丢弃,因为对应的请求已经因为超时被释放掉,服务端的这个响应会因为找不到对应的请求而被丢弃。既然响应在请求超时之后返回给客户端会被丢弃,那么在确定请求已经超时的情况下服务端是否可以不处理这个请求而直接返回超时的响应给客户端?——这就是 SOFABolt 的快速失败机制。

    快速失败机制

    快速失败机制可以减轻服务端的负担,使服务端尽快恢复服务。比如因为某些外部依赖的因素导致服务端处理一批请求产生了阻塞,而此时客户端还在将更多的请求发送到服务端堆积在 Buffer 中等待处理。当外部依赖恢复时,服务端因为要处理已经在 Buffer 中的请求(实际这些请求已经超时,处理这些请求将没有业务意义),而导致后续正常的请求排队阻塞。加入快速失败机制后,在这种情况下可以将 Buffer 中的请求进行丢弃而开始服务当前新增的未超时的请求,使的服务能快速的恢复。

    快速失败机制-2

    快速失败机制的前提条件是能判断出一个请求已经超时,而判断超时需要依赖时间,依赖时间则需要统一的时间参照。在分布式系统中是无法依赖不同的机器上的时间的,因为网络会有延迟、机器时间的时间会有偏差。为了避免参照时间的不一致(机器之间的时钟不一致),SOFABolt 的快速失败机制只依赖于服务端机器自身的时钟(统一的时间参照),判断请求已经超时的条件为:

    System.currentTimestamp - request.arriveTimestamp > request.timeout

    request.arriveTimestamp 为请求达到服务端时的时间,request.timeout 为请求设置的超时时间,因为请求从客户端发出到服务端需要时间,所以当以到达时间来计算时,如果这个请求已经超时,那么这个请求在客户端侧必然已经超时,可以安全的将这个请求丢弃。

    具体分布式系统中时间和顺序等相关的文件推荐阅读《Time, Clocks, and the Ordering of Events in a Distributed System》,Lamport 在此文中透彻的分析了分布式系统中的顺序问题。

    协议框架

    协议框架

    SOFABolt 中包含的协议命令如上图所示。在 RPC 版本的协议命令中只包含两类:RPC 请求/响应、心跳的请求/响应。RPC 的请求/响应负责携带用户的请求数据和响应数据,心跳请求用于连接的保活,只携带少量的信息(一般只包含请求 ID 之类的必要信息即可)。

    有了命令之后,还需要有命令的编解码器和命令处理器,以实现命令的编解码和处理。RemotingCommand 的处理模型如下:

    emotingCommand 的处理模型

    整个请求和响应的过程设计的核心组件如上图所示,其中:

    • 客户端侧:

      • Connection 连接对象的封装,封装对底层网络的操作;
      • CommandEncoder 负责编码 RemotingCommand,将 RemotingCommand 按照私有协议编码成 byte 数据;
      • RpcResponseProcessor 负责处理服务端的响应;
    • 服务端侧:

      • CommandDecoder 分别负责解码 byte 数据,按照私有协议将 byte 数据解析成 RemotingCommand 对象;
      • RpcHandler 按照协议码将 RemotingCommand 转发到对应的 CommandHandler 处理;
      • CommandHandler 按照 CommandCode 将 RemotingCommand 转发到对应的 RpcRequestProcessor 处理;
      • RpcRequestProcessor 按照 RemotingCommand 携带对象的 Class 将请求转发到用户的 UserProcessor 执行业务逻辑,并将结果通过 CommandDecoder 编码后返回给客户端;

    私有协议实现

    内置私有协议实现

    SOFABolt 除了提供基础通信能力外,内置了私有协议的实现,可以做到开箱即用。内置的私有协议实现是经过实践打磨的,具备扩展性的私有协议实现。

    内置私有协议

    • proto:预留的协议码字段,当协议发生较大变更时,可以通过协议码进行区分;
    • ver1:确定协议之后,通过协议版本来兼容未来协议的小调整,比如追加字段;
    • type:标识 Command 类型:oneway、request、response;
    • cmdcode:命令码,比如之前介绍的 RpcRequestCommand、HeartbeatCommand 就需要用不同的命令码进行区分;
    • ver2:Command 的版本,用于标识同一个命令的不同版本;
    • requestId:请求的 ID,用于唯一标识一个请求,在异步操作中通过此 ID 来映射请求和响应;
    • codec:序列化码,用于标识使用哪种方式来进行业务数据的序列化;
    • switch:协议开关,用于标识是否开启某些协议层面的能力,比如开启 CRC 校验;
    • timeout:客户端进行请求时设置的超时时间,快速失败机制所依赖的超时时间;
    • classLen:业务请求类的的类名长度;
    • headerLen:业务请求头的长度;
    • contentLen:业务请求体的长度;
    • className:业务请求类的类名;
    • header:业务请求头;
    • content:业务请求体;
    • CRC32:CRC校验码;

    实现自定义协议

    在 SOFABolt 中实现私有协议的关键是实现编解码器(CommandEncoder/CommandDecoder)及命令处理器(CommandHandler)。

    编解码器

    上面是为了在 SOFABolt 中实现自定义私有协议锁需要编写的类。SOFABolt 将编解码器及命令处理器都绑定到 Protocol 对象上,每个 Protocol 实现都有一组自己的编解码器和命令处理器。

    Protocol 对象

    在编解码器中实现自定义的私有协议。在设计私有协议时一定要考虑好协议的可拓展性,以便在未来进行功能增强时不会出现协议无法兼容的情况。

    可拓展性

    完成编解码之后剩余工作就是实现处理器。处理器分为两块:命令处理入口 CommandHandler 及具体的业务逻辑执行器 RemotingProcessor。

    命令处理入口 CommandHandler

    具体的业务逻辑执行器 RemotingProcessor

    完成以上工作后,使用 SOFABolt 实现自定义私有协议通信的开发工作基本完成了,但是在实际编写这部分代码时会遇到种种困难及限制,主要体现在以下一些方面:

    • 扩展性不足:比如在 RpcClient 中默认使用了内置的编解码器,且没有预留接口进行设置,当使用自定义协议时只能继承 RpcClient 进行覆盖;
    • 框架和协议耦合:比如默认提供了 CommandHandler->RemotingProcessor->UserProcessor 这样的处理模型,但是这个模型和协议耦合严重(依赖于 CommandCode 和 RequestCode),导致使用自定义协议时只能自己实现 CommandHandler,然后自己在实现请求的分发逻辑等,相当于要重写 CommandHandler->RemotingProcessor->UserProcessor 这个模型;
    • 协议限制:虽然可以通过自定义 Encoder 和 Decoder 实现自定义协议,但是框架内部组织时都依赖 ProtocolCode,导致需要将 ProtocolCode 加入到协议中,限制了用户设计私有协议的自由;

    总体而言,当前 SOFABolt 提供了非常强大的通信能力和多年沉淀的协议设计。如果用户需要去适配自己当前已经在运行的私有协议还有可以完善的地方,根本原因还是在于设计之初是贴合这 RPC 框架来设计的(从很多代码的命名上也能看出来),所以在协议和框架的分离上可以做的更好。

    总结

    本次分享从 SOFABolt 整体框架的实现开始,介绍了 SOFABolt 的基础通信模型、超时控制以及快速失败机制,着重分析了私有协议实现的示例,总结而言 SOFABolt 提供了:

    • 基于 Netty 的最佳实践;
    • 基础的通信模型和高效的超时控制机制、快速失败机制;
    • 内置的私有协议实现,开箱即用;

    欢迎 Star SOFABolt:https://github.com/sofastack/sofa-bolt

    以上就是本期分享的主要内容。因为直播时间有限,关于 SOFABolt 更详细的介绍,可以阅读「剖析 SOFABolt 框架」系列文章,由 SOFABolt 团队以及开源社区同学共同出品:

    「剖析 SOFABolt 框架」解析:https://www.sofastack.tech/blog/ 点击 tag 「剖析 | SOFABolt 框架」

    one more thing

    SOFABolt 目前也存在可以提升完善的地方,在尝试实现完全自定义的私有协议时是相对困难的,需要对代码做一些继承改造。

    针对这个现状,我们在“阿里巴巴编程之夏”活动中提交了一个 SOFABolt 的课题:“拆分 SOFABolt 的框架和协议”,希望先通过拆分框架和协议,之后再进行模块化的处理,使 SOFABolt 成为一个灵活的、可拓展的通信框架最佳实践!

    欢迎大家一起共建来解决这个问题,让 SOFABolt 变得更好:
    https://github.com/sofastack/sofa-bolt/issues/224

    SOFAStack 也欢迎更多开源爱好者加入社区共建,成为社区 Contributor、Committer(emoji 表情)

    SOFACommunity:https://www.sofastack.tech/community/

    本期视频回顾以及 PPT 查看地址

    https://tech.antfin.com/community/live/1265

    ]]>
    阿里云《云原生架构白皮书》:云计算的最佳演进路径 Fri, 20 Jun 2025 02:20:33 +0800 “云原生”已成为一个成为技术圈广泛传播的流行词了。那么什么是云原生?云原生能给我们带来什么?怎么将云原生架构落地?应该是每个关心云计算新技术的技术人都关心的吧,我也不例外,当在群里得知阿里云要出品《云原生架构白皮书》时,第一时间就预约了试读。
    PS:做个小广告,如果你也想拥有这样的“特权”,欢迎加入阿里云MVP]]>
    首届 KubeCon 2020 线上峰会-阿里云专场议程全公布 Fri, 20 Jun 2025 02:20:33 +0800 2020 年 7 月 30 日- 8 月 1 日,由 Cloud Native Computing Foundation (简称 CNCF ) 主办的云原生技术大会 Cloud Native + Open Source Virtual Summit China 2020 (以下简称线上 KubeCon )将首次以线上形式召开。

    作为云原生领域重要的知识分享和实践体验平台,阿里云应邀出席首届线上 KubeCon ,同时推出“阿里巴巴云原生专场”整场活动全天候直播,共 27 个议题,37 位云原生专家,超过 780 分钟干货分享最新云原生热点话题

    详细议程现已公布,详见下方海报。
    C1474728-0B68-4699-B55E-6FBCA7A44860.png

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

    ]]>
    ​Kafka分区分配策略 Fri, 20 Jun 2025 02:20:33 +0800 我们知道每个Topic会分配为很多partitions,Producers会将数据分配到每个partitions中,然后消费者Consumers从partitions中获取数据消费,那么Producers是如何将数据分到partitions中?Consumers又怎么知道从哪个partitions中消费数据?

    生产者往Topic写数据

    我们从product.send方法入手,看看里面的具体实现,可以看到在调用send方法时,其内部是调用了doSend方法,在doSend方法中有一个获取partitions的方法

    int partition = partition(record, serializedKey, serializedValue, cluster);
    
    private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
        Integer partition = record.partition();
        return partition != null ?
                partition :
                partitioner.partition(
                        record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
    }

    从上面代码中,首先先选择配置的分区,如果没有配置则使用默认的分区,即使用了DefaultPartitioner中的partition方法

    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // 没有可用的分区,则给一个不可用分区
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // 根据key的hash值和分区数取模
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

    上面代码先会根据topic获取所有的分区
    1,如果key为null,则通过先产生随机数,之后在该数上自增的方式产生一个数nextValue,如果存在可用分区,将nextValue转为正数之后对可用分区进行取模操作,如果不存在可用分区,则将nextValue对总分区数进行取模操作

    2,如果key不为空,就先获取key的hash值,然后和分区数进行取模操作

    消费者从Topic读数据
    kafka默认对消费分区指定了两种策略,分别为Range策略(org.apache.kafka.clients.consumer.RangeAssignor)和RoundRobin策略(org.apache.kafka.clients.consumer.RoundRobinAssignor),它们都实现了PartitionAssignor接口

    Range策略
    比如有10个分区,分别为P1、P2、P3、P4、P5、P6、P7、P8、P9、P10,三个消费者C1、C2、C3,消费如下图:
    image.png
    我们来看看源代码:

    @Override
    public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic,
                                                    Map<String, Subscription> subscriptions) {
        // 得到topic和订阅的消费者集合信息,例如{t1:[c1,c2,c3], t2:[c1,c2,c3,c4]}
        Map<String, List<String>> consumersPerTopic = consumersPerTopic(subscriptions);
        Map<String, List<TopicPartition>> assignment = new HashMap<>();
        // 将consumersPerTopic信息转换为assignment,memberId就是消费者client.id+uuid(kafka在client.id上追加的)
        for (String memberId : subscriptions.keySet())
            assignment.put(memberId, new ArrayList<TopicPartition>());
    
        // 遍历每个Topic,获取所有的订阅消费者
        for (Map.Entry<String, List<String>> topicEntry : consumersPerTopic.entrySet()) {
            String topic = topicEntry.getKey();
            List<String> consumersForTopic = topicEntry.getValue();
    
            Integer numPartitionsForTopic = partitionsPerTopic.get(topic);
            // 如果Topic没有分区,则调过
            if (numPartitionsForTopic == null)
                continue;
    
             // 将Topic的订阅者根据字典排序
            Collections.sort(consumersForTopic);
             // 总分区数/订阅者的数量 得到每个订阅者应该分配分区数
            int numPartitionsPerConsumer = numPartitionsForTopic / consumersForTopic.size();
            // 无法整除的剩余分区数量
            int consumersWithExtraPartition = numPartitionsForTopic % consumersForTopic.size();
    
            List<TopicPartition> partitions = AbstractPartitionAssignor.partitions(topic, numPartitionsForTopic);
            //遍历所有的消费者
            for (int i = 0, n = consumersForTopic.size(); i < n; i++) {
                  //分配到的分区的开始位置
                int start = numPartitionsPerConsumer * i + Math.min(i, consumersWithExtraPartition);
                // 分配到的分区数量(整除分配到的分区数量,加上1个无法整除分配到的分区--如果有资格分配到这个分区的话。判断是否有资格分配到这个分区:如果整除后余数为m,那么排序后的消费者集合中前m个消费者都能分配到一个额外的分区)
                int length = numPartitionsPerConsumer + (i + 1 > consumersWithExtraPartition ? 0 : 1);
                //给消费者分配分区
                assignment.get(consumersForTopic.get(i)).addAll(partitions.subList(start, start + length));
            }
        }
        return assignment;
    }

    上面的代码添加了注释很清楚的展现了range的实现,对应上面的例子,如果有4个消费者C1、C2、C3、C4,那么根据上面的算法:

    C1 -> [P1,P2,P3] ,C2 -> [P4,P5,P6] ,C3 -> [P7,P8] C4 -> [P9,P10] 。取余多出来的两个分区,由最前n个消费者来消费

    RoundRobin策略
    将主题的所有分区依次分配给消费者,比如有两个Topic:T1[P1,P2,P3,P4],T2[P5,P6,P7,P8,P9,P10],若C1、C2订阅了T1,C2、C3订阅了T2,那么C1将消费T1[P1,P3],C2将消费T1[P2,P4,P6,P8,P10],C3将消费T2[P5,P7,P9],如下图:
    image.png

    @Override
    public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic,
                                                    Map<String, Subscription> subscriptions) {
        Map<String, List<TopicPartition>> assignment = new HashMap<>();
        for (String memberId : subscriptions.keySet())
            assignment.put(memberId, new ArrayList<TopicPartition>());
        // 将消费集合先按字典排序,构建成一个环形迭代器
        CircularIterator<String> assigner = new CircularIterator<>(Utils.sorted(subscriptions.keySet()));
       // 按Topic的名称排序,得到Topic下的所有分区
        for (TopicPartition partition : allPartitionsSorted(partitionsPerTopic, subscriptions)) {
            final String topic = partition.topic();
    
            while (!subscriptions.get(assigner.peek()).topics().contains(topic))
                assigner.next();
            // 给消费者分配分区,并轮询到下一个消费者
            assignment.get(assigner.next()).add(partition);
        }
        return assignment;
    }
    
    /**
     * 根据消费者得到订阅的Topic下的所有分区
     * Topic按名称字典排序
     */
    public List<TopicPartition> allPartitionsSorted(Map<String, Integer> partitionsPerTopic,
                                                    Map<String, Subscription> subscriptions) {
        SortedSet<String> topics = new TreeSet<>();
        for (Subscription subscription : subscriptions.values())
            topics.addAll(subscription.topics());
    
        List<TopicPartition> allPartitions = new ArrayList<>();
        for (String topic : topics) {
            Integer numPartitionsForTopic = partitionsPerTopic.get(topic);
            if (numPartitionsForTopic != null)
                allPartitions.addAll(AbstractPartitionAssignor.partitions(topic, numPartitionsForTopic));
        }
        return allPartitions;
    }
    ]]>
    Spring中异步注解@Async的使用、原理及使用时可能导致的问题 Fri, 20 Jun 2025 02:20:33 +0800 前言

    其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章:

    面试必杀技,讲一讲Spring中的循环依赖

    然后,很多同学碰到了下面这个问题,添加了Spring提供的一个异步注解@Async循环依赖无法被解决了,下面是一些读者的留言跟群里同学碰到的问题:

    image-20200719200303749

    image-20200719200244719

    本着讲一个知识点就要讲明白、讲透彻的原则,我决定单独写一篇这样的文章对@Async这个注解做一下详细的介绍,这个注解带来的问题远远不止循环依赖这么简单,如果对它不够熟悉的话建议慎用。

    文章要点

    image-20200719201511174

    @Async的基本使用

    这个注解的作用在于可以让被标注的方法异步执行,但是有两个前提条件

    1. 配置类上添加@EnableAsync注解
    2. 需要异步执行的方法的所在类由Spring管理
    3. 需要异步执行的方法上添加了@Async注解

    我们通过一个Demo体会下这个注解的作用吧

    第一步,配置类上开启异步:

    @EnableAsync
    @Configuration
    @ComponentScan("com.dmz.spring.async")
    public class Config {
    
    }

    第二步,

    @Component  // 这个类本身要被Spring管理
    public class DmzAsyncService {
        
        @Async  // 添加注解表示这个方法要异步执行
        public void testAsync(){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("testAsync invoked");
        }
    }

    第三步,测试异步执行

    public class Main {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
            DmzAsyncService bean = ac.getBean(DmzAsyncService.class);
            bean.testAsync();
            System.out.println("main函数执行完成");
        }
    }
    // 程序执行结果如下:
    // main函数执行完成
    // testAsync invoked

    通过上面的例子我们可以发现,DmzAsyncService中的testAsync方法是异步执行的,那么这背后的原理是什么呢?我们接着分析

    原理分析

    我们在分析某一个技术的时候,最重要的事情是,一定一定要找到代码的入口,像Spring这种都很明显,入口必定是在@EnableAsync这个注解上面,我们来看看这个注解干了啥事(本文基于5.2.x版本)

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    // 这里是重点,导入了一个ImportSelector
    @Import(AsyncConfigurationSelector.class)
    public @interface EnableAsync {
        
        // 这个配置可以让程序员配置需要被检查的注解,默认情况下检查的就是@Async注解
        Class<? extends Annotation> annotation() default Annotation.class;
        
        // 默认使用jdk代理
        boolean proxyTargetClass() default false;
        
        // 默认使用Spring AOP
        AdviceMode mode() default AdviceMode.PROXY;
        
        // 在后续分析我们会发现,这个注解实际往容器中添加了一个
        // AsyncAnnotationBeanPostProcessor,这个后置处理器实现了Ordered接口
        // 这个配置主要代表了AsyncAnnotationBeanPostProcessor执行的顺序
        int order() default Ordered.LOWEST_PRECEDENCE;
    }
    

    上面这个注解做的最重要的事情就是导入了一个AsyncConfigurationSelector,这个类的源码如下:

    public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    
        private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
                "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
    
        @Override
        @Nullable
        public String[] selectImports(AdviceMode adviceMode) {
            switch (adviceMode) {
                    // 默认会使用SpringAOP进行代理
                case PROXY:
                    return new String[] {ProxyAsyncConfiguration.class.getName()};
                case ASPECTJ:
                    return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
                default:
                    return null;
            }
        }
    
    }

    这个类的作用是像容器中注册了一个ProxyAsyncConfiguration,这个类的继承关系如下:

    image-20200719220316319

    我们先看下它的父类AbstractAsyncConfiguration,其源码如下:

    @Configuration
    public abstract class AbstractAsyncConfiguration implements ImportAware {
        
        @Nullable
        protected AnnotationAttributes enableAsync;
    
        @Nullable
        protected Supplier<Executor> executor;
    
        @Nullable
        protected Supplier<AsyncUncaughtExceptionHandler> exceptionHandler;
        
        // 这里主要就是检查将其导入的类上是否有EnableAsync注解
        // 如果没有的话就报错
        @Override
        public void setImportMetadata(AnnotationMetadata importMetadata) {
            this.enableAsync = AnnotationAttributes.fromMap(
                    importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
            if (this.enableAsync == null) {
                throw new IllegalArgumentException(
                        "@EnableAsync is not present on importing class " + importMetadata.getClassName());
            }
        }
        
        // 将容器中配置的AsyncConfigurer注入
        // 异步执行嘛,所以我们可以配置使用的线程池
        // 另外也可以配置异常处理器
        @Autowired(required = false)
        void setConfigurers(Collection<AsyncConfigurer> configurers) {
            if (CollectionUtils.isEmpty(configurers)) {
                return;
            }
            if (configurers.size() > 1) {
                throw new IllegalStateException("Only one AsyncConfigurer may exist");
            }
            AsyncConfigurer configurer = configurers.iterator().next();
            this.executor = configurer::getAsyncExecutor;
            this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;
        }
    
    }

    再来看看ProxyAsyncConfiguration这个类的源码

    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
    
        @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
            AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
            // 将通过AsyncConfigurer配置好的线程池跟异常处理器设置到这个后置处理器中
            bpp.configure(this.executor, this.exceptionHandler);
            Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
            if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
                bpp.setAsyncAnnotationType(customAsyncAnnotation);
            }
            bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
            bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
            return bpp;
        }
    
    }

    这个类本身是一个配置类,它的作用是向容器中添加一个AsyncAnnotationBeanPostProcessor。到这一步我们基本上就可以明白了,@Async注解的就是通过AsyncAnnotationBeanPostProcessor这个后置处理器生成一个代理对象来实现异步的,接下来我们就具体看看AsyncAnnotationBeanPostProcessor是如何生成代理对象的,我们主要关注一下几点即可:

    1. 是在生命周期的哪一步完成的代理?
    2. 切点的逻辑是怎么样的?它会对什么样的类进行拦截?
    3. 通知的逻辑是怎么样的?是如何实现异步的?

    基于上面几个问题,我们进行逐一分析

    是在生命周期的哪一步完成的代理?

    我们抓住重点,AsyncAnnotationBeanPostProcessor是一个后置处理器器,按照我们对Spring的了解,大概率是在这个后置处理器的postProcessAfterInitialization方法中完成了代理,直接定位到这个方法,这个方法位于父类AbstractAdvisingBeanPostProcessor中,具体代码如下:

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 没有通知,或者是AOP的基础设施类,那么不进行代理
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
            return bean;
        }
        
        // 对已经被代理的类,不再生成代理,只是将通知添加到代理类的逻辑中
        // 这里通过beforeExistingAdvisors决定是将通知添加到所有通知之前还是添加到所有通知之后
        // 在使用@Async注解的时候,beforeExistingAdvisors被设置成了true
        // 意味着整个方法及其拦截逻辑都会异步执行
        if (bean instanceof Advised) {
            Advised advised = (Advised) bean;
            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                }
                else {
                    advised.addAdvisor(this.advisor);
                }
                return bean;
            }
        }
        
        // 判断需要对哪些Bean进行来代理
        if (isEligible(bean, beanName)) {
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            if (!proxyFactory.isProxyTargetClass()) {
                evaluateProxyInterfaces(bean.getClass(), proxyFactory);
            }
            proxyFactory.addAdvisor(this.advisor);
            customizeProxyFactory(proxyFactory);
            return proxyFactory.getProxy(getProxyClassLoader());
        }
        return bean;
    }

    果不其然,确实是在这个方法中完成的代理。接着我们就要思考,切点的过滤规则是什么呢?

    切点的逻辑是怎么样的?

    其实也不难猜到肯定就是类上添加了@Async注解或者类中含有被@Async注解修饰的方法。基于此,我们看看这个isEligible这个方法的实现逻辑,这个方位位于AbstractBeanFactoryAwareAdvisingPostProcessor中,也是AsyncAnnotationBeanPostProcessor的父类,对应代码如下:

    // AbstractBeanFactoryAwareAdvisingPostProcessor的isEligible方法
    // 调用了父类
    protected boolean isEligible(Object bean, String beanName) {
        return (!AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) &&
                super.isEligible(bean, beanName));
    }
    
    protected boolean isEligible(Object bean, String beanName) {
        return isEligible(bean.getClass());
    }
    
    protected boolean isEligible(Class<?> targetClass) {
        Boolean eligible = this.eligibleBeans.get(targetClass);
        if (eligible != null) {
            return eligible;
        }
        if (this.advisor == null) {
            return false;
        }
        // 这里完成的判断
        eligible = AopUtils.canApply(this.advisor, targetClass);
        this.eligibleBeans.put(targetClass, eligible);
        return eligible;
    }

    实际上最后就是根据advisor来确定是否要进行代理,在Spring中AOP相关的API及源码解析,原来AOP是这样子的这篇文章中我们提到过,advisor实际就是一个绑定了切点的通知,那么AsyncAnnotationBeanPostProcessor这个advisor是什么时候被初始化的呢?我们直接定位到AsyncAnnotationBeanPostProcessorsetBeanFactory方法,其源码如下:

    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);
        
        // 在这里new了一个AsyncAnnotationAdvisor
        AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
        if (this.asyncAnnotationType != null) {
            advisor.setAsyncAnnotationType(this.asyncAnnotationType);
        }
        advisor.setBeanFactory(beanFactory);
        // 完成了初始化
        this.advisor = advisor;
    }

    我们来看看AsyncAnnotationAdvisor中的切点匹配规程是怎么样的,直接定位到这个类的buildPointcut方法中,其源码如下:

    protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {
        ComposablePointcut result = null;
        for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {
            // 就是根据这两个匹配器进行匹配的
            Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
            Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);
            if (result == null) {
                result = new ComposablePointcut(cpc);
            }
            else {
                result.union(cpc);
            }
            result = result.union(mpc);
        }
        return (result != null ? result : Pointcut.TRUE);
    }

    代码很简单,就是根据cpc跟mpc两个匹配器来进行匹配的,第一个是检查类上是否有@Async注解,第二个是检查方法是是否有@Async注解。

    那么,到现在为止,我们已经知道了它在何时创建代理,会为什么对象创建代理,最后我们还需要解决一个问题,代理的逻辑是怎么样的,异步到底是如何实现的?

    通知的逻辑是怎么样的?是如何实现异步的?

    前面也提到了advisor是一个绑定了切点的通知,前面分析了它的切点,那么现在我们就来看看它的通知逻辑,直接定位到AsyncAnnotationAdvisor中的buildAdvice方法,源码如下:

    protected Advice buildAdvice(
        @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
    
        AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
        interceptor.configure(executor, exceptionHandler);
        return interceptor;
    }

    简单吧,加了一个拦截器而已,对于interceptor类型的对象,我们关注它的核心方法invoke就行了,代码如下:

    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
        final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        
        // 异步执行嘛,先获取到一个线程池
        AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
        if (executor == null) {
            throw new IllegalStateException(
                "No executor specified and no default executor set on AsyncExecutionInterceptor either");
        }
        
        // 然后将这个方法封装成一个 Callable对象传入到线程池中执行
        Callable<Object> task = () -> {
            try {
                Object result = invocation.proceed();
                if (result instanceof Future) {
                    return ((Future<?>) result).get();
                }
            }
            catch (ExecutionException ex) {
                handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
            }
            catch (Throwable ex) {
                handleError(ex, userDeclaredMethod, invocation.getArguments());
            }
            return null;
        };
        // 将任务提交到线程池
        return doSubmit(task, executor, invocation.getMethod().getReturnType());
    }

    导致的问题及解决方案

    问题1:循环依赖报错

    就像在这张图里这个读者问的问题,image-20200719200303749

    分为两点回答:

    第一:循环依赖为什么不能被解决?

    这个问题其实很简单,在《面试必杀技,讲一讲Spring中的循环依赖》这篇文章中我从两个方面分析了循环依赖的处理流程

    1. 简单对象间的循环依赖处理
    2. AOP对象间的循环依赖处理

    按照这种思路,@Async注解导致的循环依赖应该属于AOP对象间的循环依赖,也应该能被处理。但是,重点来了,解决AOP对象间循环依赖的核心方法是三级缓存,如下:

    image-20200706105535307

    在三级缓存缓存了一个工厂对象,这个工厂对象会调用getEarlyBeanReference方法来获取一个早期的代理对象的引用,其源码如下:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
       Object exposedObject = bean;
       if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
          for (BeanPostProcessor bp : getBeanPostProcessors()) {
              // 看到这个判断了吗,通过@EnableAsync导入的后置处理器
              // AsyncAnnotationBeanPostProcessor根本就不是一个SmartInstantiationAwareBeanPostProcessor
              // 这就意味着即使我们通过AsyncAnnotationBeanPostProcessor创建了一个代理对象
              // 但是早期暴露出去的用于给别的Bean进行注入的那个对象还是原始对象
             if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
             }
          }
       }
       return exposedObject;
    }

    看完上面的代码循环依赖的问题就很明显了,因为早期暴露的对象跟最终放入容器中的对象不是同一个,所以报错了。报错的具体位置我在你知道Spring是怎么将AOP应用到Bean的生命周期中的吗? 文章末尾已经分析过了,本文不再赘述

    image-20200720152830307

    解决方案

    就以上面读者给出的Demo为例,只需要在为B注入A时添加一个@Lazy注解即可

    @Component
    public class B implements BService {
        
        @Autowired
        @Lazy
        private A a;
    
        public void doSomething() {
        }
    }

    这个注解的作用在于,当为B注入A时,会为A生成一个代理对象注入到B中,当真正调用代理对象的方法时,底层会调用getBean(a)去创建A对象,然后调用方法,这个注解的处理时机是在org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency方法中,处理这个注解的代码位于org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy,这些代码其实都在我之前的文章中分析过了

    Spring杂谈 | Spring中的AutowireCandidateResolver

    谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

    所以本文不再做详细分析

    问题2:默认线程池不会复用线程

    我觉得这是这个注解最坑的地方,没有之一!我们来看看它默认使用的线程池是哪个,在前文的源码分析中,我们可以看到决定要使用线程池的方法是org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor。其源码如下:

    protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
        AsyncTaskExecutor executor = this.executors.get(method);
        if (executor == null) {
            Executor targetExecutor;
            // 可以在@Async注解中配置线程池的名字
            String qualifier = getExecutorQualifier(method);
            if (StringUtils.hasLength(qualifier)) {
                targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
            }
            else {
                // 获取默认的线程池
                targetExecutor = this.defaultExecutor.get();
            }
            if (targetExecutor == null) {
                return null;
            }
            executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
                        (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
            this.executors.put(method, executor);
        }
        return executor;
    }

    最终会调用到org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor这个方法中

    protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
       Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
       return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
    }

    可以看到,它默认使用的线程池是SimpleAsyncTaskExecutor。我们不看这个类的源码,只看它上面的文档注释,如下:

    image-20200720160047340

    主要说了三点

    1. 为每个任务新起一个线程
    2. 默认线程数不做限制
    3. 不复用线程

    就这三点,你还敢用吗?只要你的任务耗时长一点,说不定服务器就给你来个OOM

    解决方案

    最好的办法就是使用自定义的线程池,主要有这么几种配置方法

    1. 在之前的源码分析中,我们可以知道,可以通过AsyncConfigurer来配置使用的线程池

    如下:

    public class DmzAsyncConfigurer implements AsyncConfigurer {
       @Override
       public Executor getAsyncExecutor() {
          // 创建自定义的线程池
       }
    }
    1. 直接在@Async注解中配置要使用的线程池的名称

    如下:

    public class A implements AService {
        
        private B b;
    
        @Autowired
        public void setB(B b) {
            System.out.println(b);
            this.b = b;
        }
    
        @Async("dmzExecutor")
        public void doSomething() {
        }
    }
    @EnableAsync
    @Configuration
    @ComponentScan("com.dmz.spring.async")
    @Aspect
    public class Config {
        @Bean("dmzExecutor")
        public Executor executor(){
            // 创建自定义的线程池
            return executor;
        }
    }

    总结

    本文主要介绍了Spring中异步注解的使用、原理及可能碰到的问题,针对每个问题文中也给出了方案。希望通过这篇文章能帮助你彻底掌握@Async注解的使用,知其然并知其所以然!

    文章有帮到你的话,记得点个赞哈~
    如果本文对你由帮助的话,记得点个赞吧!也欢迎关注我的公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。

    我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

    ]]>
    Kafka offset commit 分析工具 Fri, 20 Jun 2025 02:20:33 +0800 问题起因

    前些天生产上的一套Kafka集群吞吐量异常高,根据Grafana监控发现主要数据TPS来自 __consumer_offsets队列。

    image.png

    其他业务TOPIC总TSP才几百+,而kafka内部Topic __consumer_offsets 达到33.85k,这现象明显不正常啊。

    排查思路

    首先怀疑是不是监控出问题了,Prometheus Exporter有bug? 还是Grafana Metrics写错了?又看了看其他集群的监控,发现并不是监控工具的问题。

    然后登陆到kafka集群后台服务器,查看一下这个topic的LOG-END-OFFSET情况,使用kafka命令行工具kafka-consumer-groups.sh,间隔5分钟采集多次,然后计算一下每秒的增量,发现和监控显示的吞吐量基本吻合。

    __consumer_offsets 22         -               2729106         -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 30         -               0               -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 8          -               2902605         -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 21         -               0               -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 4          -               26901884        -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 27         -               1173895         -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 7          -               829529641       -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 9          -               1788460542      -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 46         -               0               -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    __consumer_offsets 25         -               0               -               consumer-10656153-9fd2bbbb-6e4f-41d1-9b60-2bbcf387bd65 /xxx.xxx.xxx    consumer-10656153
    ......

    顺便说一下Kafka的内部队列 __consumer_offsets的作用,kafka默认将这个topic分成了50个partition,用于存储consumer group每次commit的offset信息,目的是为了在consumer重启后或者在增减消费者时,可以根据此offset信息继续消费。
    Consumer Group 对应Partition计算规则:Math.abs(groupID.hashCode()) % numPartitions
    Topic中存储的消息格式:[Group, Topic, Partition]::[OffsetMetadata[Offset, Metadata], CommitTime, ExpirationTime]

    分析到了这里,造成__consumer_offsets吞吐量过高的真相只有一个了,就是业务端的应用服务中的consumer group提交的频次过高。

    既然已经定位了问题了 ,那么去追查具体服务就OK了吧,现实情况显然不是这样的,使用这套kafka集群的平台是一个非常庞大的业务系统,150+的微服务,具体哪些服务和kafka相关,哪些是消费者,估计都得梳理几天。。。

    分析工具

    既然已经知道问题产生的原因,同时也了解kafka内部队列__consumer_offsets的存储策略,那么写个程序去读取该topic的消息,然后分析哪些consumer group的提交频次过高,根据group name便可以直接定位具体是哪个微服务了。

    coding......

    开始表演show time.......

    根据定位到的异常微服务排查发现,有使用offset自动提交,但是auto.commit.interval设置了100ms,也有使用手动提交offset,但无数据消费时仍然提交offset。。。太坑了

    此后,代码质量的checkpoint项中增加关于kafka使用的检查。

    最后分享一下工具GitHub地址:kafka-offset-consumer
    image.png

    ]]>
    你也被Spring的这个“线程池”坑过吗? Fri, 20 Jun 2025 02:20:33 +0800

    原文链接

    前两天一个晚上,正当我沉浸在敲代码的快乐中时,听到隔壁的同事传来一声不可置信的惊呼:线程池提交命令怎么可能会执行一秒多?
    线程池提交方法执行一秒多?那不对啊,线程池提交应该是一个很快的操作,一般情况下不应该执行一秒多那么长的时间。
    看了一下那段代码,好像也没什么问题,就是一个简单的提交任务的代码。

    executor.execute( () -> {
        // 具体的任务代码
        // 这里有个for循环
    });

    虽然执行的Job里面有一个for循环,可能比较耗时,但是execute提交任务的时候,并不会去真正去执行Job,所以应该不是这个原因引起的。

    分析

    看到这个情况,我们首先想到的是线程池提交任务时候的一个处理过程:
    image.png

    然后逐个分析一下有可能耗时一秒多的操作:
    创建线程耗时?
    根据上面的图,我们可以知道,如果核心线程数量设置过大,就可能会不断创建新的核心线程去执行任务。同理,如果核心线程池和任务队列都满了,会创建非核心线程去执行任务。
    创建线程是比较耗时的,而且Java线程池在这里创建线程的时候还上了锁。

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    

    我们写个简单的程序,可以模拟出来线程池耗时的操作,下面这段代码创建2w个线程,在我的电脑里大概会耗时6k多毫秒。

    long before = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        // doSomething里面睡眠一秒
        new Thread(() -> doSomething()).start();
    }
    long after = System.currentTimeMillis();
    // 下面这行在我的电脑里输出6139
    System.out.println(after - before);

    但是看了一下我们的监控,线程数量一直比较健康,应该不是这个原因。再说那个地方新线程也不太可能达到这个量级。
    入任务队列耗时?
    线程池的任务队列是一个同步队列。所以入队列操作是同步的。
    常用的几个同步队列:

    1. LinkedBlockingQueue
      链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小。
    2. ArrayBlockingQueue
      数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
    3. SynchronousQueue
      同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
    4. DelayQueue
      延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。

    所以使用特殊的同步队列还是有可能导致execute方法阻塞一秒多的,比如SynchronousQueue。如果配合一个特殊的“拒绝策略”,是有可能造成这个现象的,我们将在下面给出例子。
    拒绝策略?
    线程数量达到最大线程数就会采用拒绝处理策略,四种拒绝处理的策略为 :

    1. ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出异常。
    2. ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。
    4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

    可以看到,前面三种拒绝处理策略都是会“丢弃”任务,而最后一种不会。最后一种拒绝策略配合上面的SynchronousQueue,就有可能造成我们遇到的情况。示例代码:

    Executor executor = new ThreadPoolExecutor(2,2, 2, 
                         TimeUnit.MILLISECONDS,new SynchronousQueue<>(), 
                         new ThreadPoolExecutor.CallerRunsPolicy());
    for (int i = 0; i < 3; i++) {
        long before = System.currentTimeMillis();
        executor.execute( () -> {
            // doSomething里面睡眠一秒
            doSomething();
        });
        long after = System.currentTimeMillis();
        // 下面这段代码,第三行会输出1001
        System.out.println(after - before);
    }

    SimpleAsyncTaskExecutor

    所以我们遇到的问题会是上面的种种原因导致的吗?带着这些猜测,我们去找到了定义executor的代码。

    SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
    executor.setConcurrencyLimit(20);

    设置最大并发数量是20好像没什么问题,等等,这个SimpleAsyncTaskExecutor是个什么鬼?
    好像是Spring提供的一个线程池吧……(声音逐渐不自信)
    em…看了一下包的定义,org.springframework.core.task,确实是Spring提供的。至于是不是线程池,先看看类图:
    image.png
    实现的是Executor接口,但是继承树里为什么没有ThreadPoolExecutor?我们猜测可能是Spring自己实现了一个线程池?虽然应该没什么必要。

    源码

    带着疑问,我们继续看了一下这个类的源码。主要看execute方法,发现每次执行之前,都要先调用一个beforeAccess方法,这个方法里面有这样一段很奇怪的代码:
    image.png
    while循环去检查,如果当前并发线程数量大于等于设置的最大值,就等待。
    找到原因了,这应该就是罪魁祸首。可是为什么Spring要这么设计呢?
    我们在SimpleAsyncTaskExecutor类的注释上面找到了作者的留言:

     * <p><b>NOTE: This implementation does not reuse threads!</b> Consider a
     * thread-pooling TaskExecutor implementation instead, in particular for
     * executing a large number of short-lived tasks.

    大概意思就是:这个实现并不复用线程,如果你要复用线程请去使用线程池的实现。这个是用来执行很多耗时很短的任务的。
    至此,真相大白。

    反思

    使用接口前先了解一下
    造成这个问题的根本原因是,我们以为SimpleAsyncTaskExecutor是一个“线程池”,而其实它不是!!!
    我们在使用开源项目的时候,往往直接就用了,不会去仔细看看它的源码,也可能没有考虑清楚它的应用环境。等到程序出问题了才发现,已经晚了。
    所以使用接口之前最好先了解一下,至少要看看官方文档或者接口文档/注释。
    哪怕是真的出问题了,看源码也不失为一种排查问题的方式,因为代码都是死的,它不会骗人。
    代码规约
    阿里有这么一个代码规约:不建议我们直接使用Executors类中的线程池,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学需要更加明确线程池的运行规则,规避资源耗尽的风险。
    以前我还不太理解,心想使用Executors类可以提高可读性,JDK提供了这样的工具类,不用白不用。直到遇到这个问题,才明白这条规约的良苦用心。
    如果我们使用规范的方式去使用线程池,而不是用一个所谓的Spring提供的“线程池”,就不会遇到这个问题了。
    明确接口职责
    再来想一想为什么同事会把它当成一个线程池?因为它的类名、方法名都太像一个线程池了。它实现了Executor接口的execute方法,才导致我们误以为它是一个线程池。
    所以回归到Executor这个接口上来,它的职责究竟是什么?我们可以在JDK的execute方法上看到这个注释:

    /**
    * Executes the given command at some time in the future.  The command
    * may execute in a new thread, in a pooled thread, or in the calling
    * thread, at the discretion of the {@code Executor} implementation.
    */

    大意就是,在将来某个时间执行传入的命令,这个命令可能会在一个新的线程里面执行,可能会在线程池里,也可能在调用这个方法的线程中,具体怎么执行是由实现类去决定的。
    所以这才是Executor这个类的职责,它的职责并不是提供一个线程池的接口,而是提供一个“将来执行命令”的接口。
    所以,真正能代表线程池意义的,是ThreadPoolExecutor类,而不是Executor接口。
    在我们写代码的时候,也要定义清楚接口的职责哟。这样别人用你的接口或者阅读源码的时候,才不会疑惑。

    来源:编了个程
    作者:Yasin x

    ]]>
    在kubernetes中用alluxio加速spark数据访问(二) Fri, 20 Jun 2025 02:20:33 +0800 相关文章:

    1.背景信息

    1.1 alluxio

    Alluxio是一个开源的基于内存的分布式存储系统,适合作为云上大数据和AI / ML的数据编排方案。Alluxio可以同时管理多个底层文件系统,将不同的文件系统统一在同一个名称空间下,让上层客户端可以自由访问统一名称空间内的不同路径,不同存储系统的数据。

    alluxio的short-circuit功能可以使alluxio客户端直接访问alluxio worker所在主机的工作存储,而不需要通过网络栈与alluxio worker完成通信,可以提高性能。

    1.2 spark operator

    Spark-operator用于管理k8s集群中spark job。通过spark-operator可以在k8s集群中创建、查看和删除spark job。

    2.前提条件

    本文档的操作依赖如下的一些条件:

    • kubernetes集群:版本大于1.8,本次实验的集群通过阿里云容器服务创建,集群名称为"ack-create-by-openapi-1"。

    image1.png

    • 安装有linux或者mac操作系统的计算机作为我们的实验环境(本次实验中,假设该计算机名称为alluxio-test)。该计算机需要准备如下环境:

      • docker >= 17.06
      • kubectl >= 1.8,能够连接kubernets集群ack-create-by-openapi-1

    3.实验步骤

    实验步骤主要包括如下几步:

    • 部署alluxio
    • 部署spark-operator
    • 制作spark docker镜像
    • 上传文件到alluxio
    • 提交spark job

    下面将对每个步骤进行说明:

    3.1 部署alluxio

    进入容器服务应用目录,在右上角的搜索框中搜索"alluxio",然后进入alluxio主界面,如图:
    image2.png

    然后选择将alluxio安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),最后点击创建,如图
    image3.png

    点击创建后,使用kubectl给待安装的alluxio组件的节点打上标签"alluxio=true",首先查看该集群有哪些节点:

    $ kubectl get nodes -o wide
    NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
    cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

    可以看到有三个worker节点,分别为:

    • cn-beijing.192.168.8.15
    • cn-beijing.192.168.8.16
    • cn-beijing.192.168.8.17

    我们给是三个节点都打上标签"alluxio=true":

    $ kubectl label nodes cn-beijing.192.168.8.15 
      cn-beijing.192.168.8.16 
      cn-beijing.192.168.8.17 
      alluxio=true

    使用kubectl查看各个pod是否都处于running状态:

    $ kubectl get po -n alluxio
    NAME                   READY   STATUS    RESTARTS   AGE
    alluxio-master-0       2/2     Running   0          4h1m
    alluxio-worker-5zg26   2/2     Running   0          4h1m
    alluxio-worker-ckmr9   2/2     Running   0          4h1m
    alluxio-worker-dvgvd   2/2     Running   0          4h1m

    验证alluxio是否处于ready:

    $ kubectl exec -ti alluxio-master-0 -n alluxio bash
    
    //下面步骤alluxio-master-0 pod中执行
    bash-4.4# alluxio fsadmin report capacity
    
    Capacity information for all workers:
        Total Capacity: 3072.00MB
            Tier: MEM  Size: 3072.00MB
        Used Capacity: 0B
            Tier: MEM  Size: 0B
        Used Percentage: 0%
        Free Percentage: 100%
    
    Worker Name      Last Heartbeat   Storage       MEM
    192.168.8.15    0                capacity      1024.00MB
                                      used          0B (0%)
    192.168.8.16    0                capacity      1024.00MB
                                      used          0B (0%)
    192.168.8.17    0                capacity      1024.00MB
                                      used          0B (0%)

    3.2 部署spark-operator

    进入容器服务应用目录,在右上角的搜索框中搜索"ack-spark-operator",然后进入ack-spark-operator主界面,如图:
    image4.png

    选择将ack-spark-operator安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),然后点击创建,如图:
    image5.png

    sparkctl是一个用于提交spark job到k8s的命令行工具,需要将sparkctl安装到我们在"前提条件"中所提到的实验环境"alluxio-test"中:

    $ wget http://spark-on-k8s.oss-cn-beijing.aliyuncs.com/sparkctl/sparkctl-linux-amd64 -O /usr/local/bin/sparkctl
    $ chmod +x /usr/local/bin/sparkctl

    3.3 制作spark docker镜像

    spark下载页面下载所需的spark版本,本次实验选择的saprk版本为2.4.6。运行如下命令下载spark:

    $ cd /root
    $ wget https://mirror.bit.edu.cn/apache/spark/spark-2.4.6/spark-2.4.6-bin-hadoop2.7.tgz
    #

    下载完成后,执行解压操作:

    $ tar -xf spark-2.4.6-bin-hadoop2.7.tgz
    $ export SPARK_HOME=/root/spark-2.4.6-bin-hadoop2.7

    spark docker镜像是我们提交spark任务时使用到的镜像,这个镜像中需要包含alluxio client jar包。使用如下的命令获取alluxio client jar包:

    $ id=$(docker create alluxio/alluxio-enterprise:2.2.1-1.4)
    $ docker cp $id:/opt/alluxio/client/alluxio-enterprise-2.2.1-1.4-client.jar 
        $SPARK_HOME/jars/alluxio-enterprise-2.2.1-1.4-client.jar
    $ docker rm -v $id 1>/dev/null

    alluxio client jar包准备好以后,开始构建镜像:

    $ docker build -t spark-alluxio:2.4.6 -f kubernetes/dockerfiles/spark/Dockerfile $SPARK_HOME

    请记住镜像名称“spark-alluxio:2.4.6”,在向k8s提交spark job中会用到这个信息。

    镜像构建完成以后,对镜像的处理有两种方式:

    • 如果有私有镜像仓库,将该镜像推送到私有镜像仓库中,同时保证k8s集群节点能够pull该镜像
    • 如果没有私有镜像仓库,那么需要使用docker save命令将该镜像导出,然后scp到k8s集群的各个节点,在每个节点上使用docker load命令将镜像导入,这样就能保证每个节点上都存在该镜像。

    3.4 上传文件到alluxio

    我们在开头提到过,本次实验是提交一个spark job到k8s中,该spark job的目标是对某一个文件统计每一个单词出现的次数,现在需要把这个文件传到alluxio存储上。这里为了方便,直接把alluxio master中/opt/alluxio-2.3.0-SNAPSHOT/LICENSE(文件路径可能因alluxio版本有点差异)这个文件传到alluxio上。

    使用"kubectl exec"进入alluxio master pod,并拷贝当前目录下的LICENSE文件到alluxio的根目录中:

    $ kubectl exec -ti alluxio-master-0  -n alluxio bash
    //下面步骤alluxio-master-0 pod中执行
    bash-4.4# alluxio fs copyFromLocal LICENSE /

    接着查看一下LICENSE这个文件分成的block被alluxio放到哪些worker上了。

    $ kubectl exec -ti alluxio-master-0 -n alluxio bash
    //下面步骤alluxio-master-0 pod中执行
    
    bash-4.4# alluxio fs stat /LICENSE
    /LICENSE is a file path.
    FileInfo{fileId=33554431, fileIdentifier=null, name=LICENSE, path=/LICENSE, ufsPath=/opt/alluxio-2.3.0-SNAPSHOT/underFSStorage/LICENSE, length=27040, blockSizeBytes=67108864, creationTimeMs=1592381889733, completed=true, folder=false, pinned=false, pinnedlocation=[], cacheable=true, persisted=false, blockIds=[16777216], inMemoryPercentage=100, lastModificationTimesMs=1592381890390, ttl=-1, lastAccessTimesMs=1592381890390, ttlAction=DELETE, owner=root, group=root, mode=420, persistenceState=TO_BE_PERSISTED, mountPoint=false, replicationMax=-1, replicationMin=0, fileBlockInfos=[FileBlockInfo{blockInfo=BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}, offset=0, ufsLocations=[]}], mountId=1, inAlluxioPercentage=100, ufsFingerprint=, acl=user::rw-,group::r--,other::r--, defaultAcl=}
    Containing the following blocks:
    BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}

    可以看到LICENSE这个文件只有一个block(id为16777216),被放在了ip为192.168.8.17的k8s节点上。我们使用kubectl查看该节点名称为cn-beijing.192.168.8.17

    $ kubectl get nodes -o wide
    NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
    cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
    cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

    3.5 提交spark job

    下面的步骤将提交一个spark job到k8s集群中,该job主要是计算alluxio中/LICENSE文件的每个单词出现的次数。

    在步骤3.4中我们获取到LICENSE这个文件所包含的block都在节点cn-beijing.192.168.8.17上,此次实验中,我们通过指定node selecor让spark driver和spark executor都运行在节点cn-beijing.192.168.8.17。同时,验证在开启alluxio的short-circuit功能的情况下,spark executor和alluxio worker之间的通信是否通过domain socket方式完成。

    • 说明:如果在开启alluxio的short-circuit功能的情况下,并且spark executor与其所要访问的文件(本次实验为/LICENSE这个文件)的block在同一个k8s节点上,那么spark executor中的alluxio client与该k8s节点上的alluxio worker之间的通信通过domain socket方式完成。

    首先生成提交spark job的yaml文件:

    $ export SPARK_ALLUXIO_IMAGE=<步骤3.3中制作的image,即spark-alluxio:2.4.6>
    $ export ALLUXIO_MASTER="alluxio-master-0"
    $ export TARGET_NODE=<步骤3.4获取到的LICENSE文件的block存储的节点,即cn-beijing.192.168.8.17>
    $ cat > /tmp/spark-example.yaml <<- EOF
    apiVersion: "sparkoperator.k8s.io/v1beta2"
    kind: SparkApplication
    metadata:
      name: spark-count-words
      namespace: default
    spec:
      type: Scala
      mode: cluster
      image: "$SPARK_ALLUXIO_IMAGE"
      imagePullPolicy: Always
      mainClass: org.apache.spark.examples.JavaWordCount
      mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.6.jar"
      arguments:
        - alluxio://${ALLUXIO_MASTER}.alluxio:19998/LICENSE
      sparkVersion: "2.4.6"
      restartPolicy:
        type: Never
      volumes:
        - name: "test-volume"
          hostPath:
            path: "/tmp"
            type: Directory
        - name: "alluxio-domain"
          hostPath:
            path: "/tmp/alluxio-domain"
            type: Directory
      driver:
        cores: 1
        coreLimit: "1200m"
        memory: "512m"
        labels:
          version: 2.4.6
        serviceAccount: spark
        volumeMounts:
          - name: "test-volume"
            mountPath: "/tmp"
          - name: "alluxio-domain"
            mountPath: "/opt/domain"
        nodeSelector:
          kubernetes.io/hostname: "$TARGET_NODE"
      executor:
        cores: 1
        instances: 1
        memory: "512m"
        labels:
          version: 2.4.6
        nodeSelector:
          kubernetes.io/hostname: "$TARGET_NODE"
        volumeMounts:
          - name: "test-volume"
            mountPath: "/tmp"
          - name: "alluxio-domain"
            mountPath: "/opt/domain"
    EOF

    然后,使用sparkctl提交spark job:

    $ sparkctl create /tmp/spark-example.yaml

    4.实验结果

    当spark driver pod处于completed状态时,使用kubectl查看spark driver的日志:

    $ kubectl get po -l spark-role=driver
    NAME                                 READY   STATUS      RESTARTS   AGE
    spark-alluxio-1592296972094-driver   0/1     Completed   0          4h33m
    
    $ kubectl logs spark-alluxio-1592296972094-driver --tail 20
    
    USE,: 3
    Patents: 2
    d): 1
    comment: 1
    executed: 1
    replaced: 1
    mechanical: 1
    20/06/16 13:14:28 INFO SparkUI: Stopped Spark web UI at http://spark-alluxio-1592313250782-driver-svc.default.svc:4040
    20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend: Shutting down all executors
    20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint: Asking each executor to shut down
    20/06/16 13:14:28 WARN ExecutorPodsWatchSnapshotSource: Kubernetes client has been closed (this is expected if the application is shutting down.)
    20/06/16 13:14:28 INFO MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped!
    20/06/16 13:14:28 INFO MemoryStore: MemoryStore cleared
    20/06/16 13:14:28 INFO BlockManager: BlockManager stopped
    20/06/16 13:14:28 INFO BlockManagerMaster: BlockManagerMaster stopped
    20/06/16 13:14:28 INFO OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped!
    20/06/16 13:14:28 INFO SparkContext: Successfully stopped SparkContext
    20/06/16 13:14:28 INFO ShutdownHookManager: Shutdown hook called
    20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /var/data/spark-2f619243-59b2-4258-ba5e-69b8491123a6/spark-3d70294a-291a-423a-b034-8fc779244f40
    20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /tmp/spark-054883b4-15d3-43ee-94c3-5810a8a6cdc7
    

    最后我们登陆到alluxio master上,查看相关指标统计到的值:

    $ kubectl exec -ti alluxio-master-0 -n alluxio bash
    //下面步骤alluxio-master-0 pod中执行
    bash-4.4# alluxio fsadmin report metrics
    Cluster.BytesReadAlluxio  (Type: COUNTER, Value: 0B)
    Cluster.BytesReadAlluxioThroughput  (Type: GAUGE, Value: 0B/MIN)
    Cluster.BytesReadDomain  (Type: COUNTER, Value: 237.66KB)
    Cluster.BytesReadDomainThroughput  (Type: GAUGE, Value: 47.53KB/MIN)
    //省略其他内容
    ......

    BytesReadAlluxio和BytesReadAlluxioThroughput代表数据从网络栈传输;BytesReadDomain和BytesReadDomainThroughput代表数据从domain socket传输。可以看到所有数据都是从domain socket传输的。

    5.参考文档

    ]]>
    基于ASM配置JWT请求授权 Fri, 20 Jun 2025 02:20:33 +0800 前提条件

    配置JWT请求授权

    环境变量

    # ASM实例kubeconfig
    MESH_CONFIG=
    # 用户ACK实例kubeconfig
    USER_CONFIG=
    # 本地istio路径
    # 可以在https://github.com/istio/istio/releases选择合适版本下载
    ISTIO_HOME=

    1 部署示例服务

    1.创建命名空间foo,并启用istio-injection

    image.png

    2.在命名空间foo中,部署官方示例服务httpbinsleep

    kubectl 
      --kubeconfig "$USER_CONFIG" 
      -n foo 
      apply -f "$ISTIO_HOME"/samples/httpbin/httpbin.yaml
    
    kubectl 
      --kubeconfig "$USER_CONFIG" 
      -n foo 
      apply -f "$ISTIO_HOME"/samples/sleep/sleep.yaml

    3.确认pod就绪前请等待。

    kubectl --kubeconfig "$USER_CONFIG" -n foo get po
      
    kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=httpbin
    kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=sleep

    4.在sleep容器内,验证是否可以请求httpbin。期待的结果是http_code值为200

    sleep_pod=$(kubectl --kubeconfig "$USER_CONFIG" get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})
    
    RESULT=$(kubectl 
      --kubeconfig "$USER_CONFIG" 
      exec "$sleep_pod" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}")
    
    if [[ $RESULT != "200" ]]; then
      echo "http_code($RESULT) should be 200"
      exit
    fi

    2. 增加请求认证

    1.在命名空间foo中,新建RequestAuthentication CRD,创建请求认证:请求httpbin服务时,须匹配jwtRules中定义的规则,即请求头中如果包含ACCESS TOKEN信息,解码后iss的值须为testing@secure.istio.iojwks中定义了TOKEN生成的相关信息,详见jwk官方文档

    image.png

    jwt-example.yaml:

    apiVersion: "security.istio.io/v1beta1"
    kind: "RequestAuthentication"
    metadata:
      name: "jwt-example"
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: httpbin
      jwtRules:
      - issuer: "testing@secure.istio.io"
        jwks: '{ "keys":[ {"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]}'

    2.验证请求认证策略生效。请求头中包含合法的ACCESS TOKEN时返回状态码200,否则返回状态码401

    for ((i = 1; i <= 5; i++)); do
        RESULT=$(kubectl 
          --kubeconfig "$USER_CONFIG" 
          exec "$sleep_pod" 
          -c sleep 
          -n foo 
          -- curl "http://httpbin.foo:8000/headers" 
          -s 
          -o /dev/null 
          -H "Authorization: Bearer invalidToken" 
          -w "%{http_code}")
        if [[ $RESULT != "401" ]]; then
          echo "http_code($RESULT) should be 401"
          exit
        fi
    done
    for ((i = 1; i <= 10; i++)); do
      RESULT=$(kubectl 
        --kubeconfig "$USER_CONFIG" 
        exec "$sleep_pod" 
        -c sleep 
        -n foo 
        -- curl "http://httpbin.foo:8000/headers" 
        -s 
        -o /dev/null 
        -w "%{http_code}")
      if [[ $RESULT != "200" ]]; then
        echo "http_code($RESULT) should be 200"
        exit
      fi
    done

    3. 增加JWT认证策略

    1.在命名空间foo中,新建AuthorizationPolicy CRD,创建JWT认证策略:请求httpbin服务时,只有请求头TOKEN解码后,符合iss的值+/+sub的值(即source.requestPrincipals)为testing@secure.istio.io/testing@secure.istio.io,请求权限才为ALLOW

    image.png

    require-jwt.yaml:

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: require-jwt
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: httpbin
      action: ALLOW
      rules:
      - from:
        - source:
           requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]

    2.TOKEN解析。

    TOKEN='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg'
    
    echo $TOKEN | cut -d '.' -f2 - | base64 --decode -

    输出信息:

    {"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}

    JWT官网提供了同样的能力:

    image.png

    3.验证JWT认证策略生效。请求头中包含合法的ACCESS TOKEN时返回状态码200,否则返回状态码403

    for ((i = 1; i <= 10; i++)); do
        RESULT=$(kubectl 
          --kubeconfig "$USER_CONFIG" 
          exec "$sleep_pod" 
          -c sleep 
          -n foo 
          -- curl "http://httpbin.foo:8000/headers" 
          -s 
          -o /dev/null 
          -H "Authorization: Bearer $TOKEN" 
          -w "%{http_code}")
        if [[ $RESULT != "200" ]]; then
          echo "http_code($RESULT) should be 200"
          exit
        fi
    done
    for ((i = 1; i <= 10; i++)); do
        RESULT=$(kubectl 
          --kubeconfig "$USER_CONFIG" 
          exec "$sleep_pod" 
          -c sleep 
          -n foo 
          -- curl "http://httpbin.foo:8000/headers" 
          -s 
          -o /dev/null 
          -w "%{http_code}")
        if [[ $RESULT != "403" ]]; then
          echo "http_code($RESULT) should be 403"
          exit
        fi
    done

    4. 追加JWT认证策略

    1.在命名空间foo中,更新JWT认证策略require-jwt,增加规则:请求httpbin服务时,只有请求头TOKEN解码后,符合groups的值包含group1,请求权限才为ALLOW

    require-jwt-group.yaml:

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: require-jwt
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: httpbin
      action: ALLOW
      rules:
      - from:
        - source:
           requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
        when:
        - key: request.auth.claims[groups]
          values: ["group1"]

    2.TOKEN解析。

    TOKEN_GROUP='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg'
    
    echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode - | jq

    输出信息:

    {
      "exp": 3537391104,
      "groups": [
        "group1",
        "group2"
      ],
      "iat": 1537391104,
      "iss": "testing@secure.istio.io",
      "scope": [
        "scope1",
        "scope2"
      ],
      "sub": "testing@secure.istio.io"
    }

    3.验证JWT认证策略生效。请求头中包含合法的ACCESS TOKEN时返回状态码200,否则返回状态码403

    for ((i = 1; i <= 10; i++)); do
        RESULT=$(kubectl 
          --kubeconfig "$USER_CONFIG" 
          exec "$sleep_pod" 
          -c sleep 
          -n foo 
          -- curl "http://httpbin.foo:8000/headers" 
          -s 
          -o /dev/null 
          -H "Authorization: Bearer $TOKEN_GROUP" 
          -w "%{http_code}")
        if [[ $RESULT != "200" ]]; then
          echo "http_code($RESULT) should be 200"
          exit
        fi
    done
    for ((i = 1; i <= 10; i++)); do
        RESULT=$(kubectl 
          --kubeconfig "$USER_CONFIG" 
          exec "$sleep_pod" 
          -c sleep 
          -n foo 
          -- curl "http://httpbin.foo:8000/headers" 
          -s 
          -o /dev/null 
          -H "Authorization: Bearer $TOKEN" 
          -w "%{http_code}")
        if [[ $RESULT != "403" ]]; then
          echo "http_code($RESULT) should be 200"
          exit
        fi
    done
    ]]>
    应用中心最佳实践之——使用应用组完成多集群一键部署 Fri, 20 Jun 2025 02:20:33 +0800 背景

    在现实中的应用交付实践中,常常需要将同一应用同时部署到多个集群中。应用中心支持将同一个数据源定义的应用,通过应用组的形式一键部署到多个集群中,实现统一管理。
    应用组是一系列应用的集合,这些应用都具有相同的数据源,但部署的目标集群或命名空间不同,相关概念如下图所示:
    image.png

    如何使用

    添加多个集群到应用中心

    进入应用中心,点击右上角“设置”,进入集群设置页面,通过添加集群功能,将希望部署应用的集群都添加到应用中心管控范围内。
    image.png

    创建应用组

    在“应用”页面中点击“创建应用”,指定应用参数并创建。
    名称将作为应用组名称,如应用组名称为app-set,应用中心将自动创建一系列应用app-set-0、app-set-1等。
    数据源可以选择Git仓库、Helm仓库或ACK编排模板。
    目标集群可以选择任意多个,每个集群可以独立指定部署的命名空间。
    image.png

    查看应用组

    创建完成后,“应用”页面将列出应用组,以及组内的全部应用。
    image.png

    部署到目标集群

    点击“部署所有应用”,可以选择整个应用组,将组内的应用同时部署到目标集群,也可以单独指定部署组内的部分应用。
    image.png

    ]]>
    日均千万级消息规模,深捷旅使用函数计算释放运维压力 Fri, 20 Jun 2025 02:20:33 +0800 深圳市捷旅国际旅行社有限公司(简称“深捷旅”),成立18年来专注于酒店行业的客房分销业务以及酒店预订信息化水平建设,通过品牌“捷旅假期”打造了业内领先的酒店B2B分销平台。深捷旅在业内旅游头部OTA企业多年深耕合作,为同程旅游、去哪儿、途牛旅游网、 Expedia,Ctrip, Agoda,驴妈妈旅游网、马蜂窝旅游、国旅运通、大唐商旅等提供全面与完善的酒店/票务服务。

    核心痛点:

    深捷旅对接的酒店超过60万+,每天都有几千万级的消息规模,而且这些消息的并发脉冲性强,时效周期短,瞬时并发处理压力大:

    1. 并发处理能力

    需要同时处理的峰值消息量超过10W+。

    2. 弹缩处理能力

    根据消息量的多少进行自动化的扩缩容操作,并且可以毫秒级进行快速的扩缩容,使用成本按所需的实际资源付费。

    3. 支持OSS,消息等多种数据源触发方式。
    4. 支持Python、Go、java 等各种编程语言。
    5. 运维监控能力

    可以支持快速部署更新,资源使用的实时监控,日志分析集成与报警能力。

    解决方案:

    函数计算可以监听多种数据源,通过监控处理业务量的变化,快速进行自适应的扩缩容操作, 通过毫秒级的扩容,可以获得线性增长的业务处理能力。函数计算支持多种编程语言,无需客户改变编程习惯。函数计算支持便捷的部署、资源使用的实时监控、日志分析集成与报警能力。
    image.png

    产品价值:

    业务稳健:函数计算可根据业务量进行扩缩容,在函数计算处理的帮助下,解决了以前按峰值资源配置方式的业务稳定性风险问题。
    运维简单:运维发布有多种工具辅助进行。以前最困扰的扩缩容资源管理部分,在使用函数计算后,完全不再需要维护,极大地释放了深捷旅开发运维人员的生产力。
    节省成本:利用函数计算的按需付费的特性,在合适的规格运行下,整个资源得到了高效利用,不为闲置付费,降低了深捷旅的总体使用成本。

    相关产品:

    阿里云函数计算:https://www.aliyun.com/product/fc?spm=5176.10695662.1112509.1.70384357oApvS9

    ]]>
    乘风破浪的云原生 Fri, 20 Jun 2025 02:20:33 +0800 作者:禾易

    1、在线教育将成为常态化应用

    “还要扩容吗?”
    “先扩容 10 倍再说”

    这已经不是李诺(洋葱学院联合创始人兼 CTO)第一次提到扩容了。受到疫情影响,今年全国学校普遍延期开学。“停课不停学”,线下教育停摆,教育行业转战线上。流量突然暴涨,扩容成了“常态”,而且每次的流量还是远远超过预期。

    李诺去找杨临风(洋葱学院联合创始人兼 CEO)讨论工作的时候,碰巧杨临风正在写一封给用户的公开信。这次疫情让洋葱学院受到了极大的关注,但比起流量价值,杨临风更想以自己的亲身经历告诉用户:“在家自主地学习,是每个学生都要面对的战斗。”

    李诺心里清楚,在超高流量的冲击下要保持服务器平稳、用户体验不受影响,这何尝不是一场属于洋葱学院的战斗。

    2013 年 12 月,杨临风、朱若辰和李诺共同创立了洋葱数学(现已更名为洋葱学院),这家K12在线教育公司从初中数学课程切入,逐步发展到全学科,主攻人机交互学习的在线教育平台开发。他们从国家课标和教材着手,开始系统地构建在线课程体系,对课本上每一个知识点进行更加精细的教研和设计,并逐个制作成5-8分钟的动画视频课程,围绕这些核心课程为学生打造个性化的学习体验。

    人机交互学习的教育模式不要说在当年,即便是现在也很前卫。不仅如此,洋葱的创始团队在公司成立之初还做出了一个意识超前的决定:整套业务系统均基于阿里云搭建。

    洋葱学院的发展速度在互联网教育公司里并不算快,李诺说,团队把大部分的精力都花在了课程的研发和学习体验的优化上,以初中数学为例,足足花了4年才完成课程的打磨。当然,洋葱学院对教育的这份坚持,也让其在业界立下了一席之地。疫情影响下,短时间剧烈增长的市场需求把在线教育推到了“快车道”。以前是在线教育企业自己努力,现在是全社会一起推进他们“质量在线”。

    今年 1 月 28 日,洋葱学院对外公布了针对疫情期间的课程捐赠方案,把过去六年积累制作的 2650 节核心课程全部免费开放,但流量的威力还是超过了他们的想象。据易观千帆的公开数据显示,洋葱学院 2020 年 2 月的活跃用户规模达到了 795.92 万,同比增幅 151%。

    面对大流量、高并发访问需求,洋葱学院为了确保业务稳定性,在阿里云技术专家的建议下,采用了阿里云容器服务。容器服务可以根据不同模块的配置所需,资源分配更加合理,按照定义规则自动弹性伸缩避免了复杂的调度维护。

    阿里云容器服务可以在几分钟内扩充底层资源,满足快速部署数千个应用实例的需求。为了更加从容地应对十倍扩容,洋葱学院还进一步优化了整体的 ECS 服务器配置,将大量的小规格 ECS 服务器更换成 30 至 50 核大规格 ECS,改造后运维管控也更加便捷。使用云容器之后,系统在资源利用率上提升了约60%,出现问题后可快速隔离,当面对急剧增长的业务量,也可以在短

    时间内扩容进行业务支撑。为了及早发现故障并快速做出响应,洋葱学院也采用了阿里云原生监控系列产品,可以覆盖到各类监控报警问题,极大地缩短问题发现时间。

    从2013年决定全面上云,到现在拥抱云原生新趋势,洋葱学院以一贯的超前意识,表达着这个时代互联网公司该有的态度。

    2、全面使用开源技术、云服务构建软件服务的时代已经到来

    云原生在近几年的发展用“乘风破浪”来形容一点也不为过。

    应用上云已经是不可逆转的趋势。回顾近年来商业世界的发展趋势,数字化转型的出现使得企业中越来越多的业务演变成数字化业务,数字化对于业务渠道、竞争格局、用户体验等诸多方面都提出更加严苛的要求,这就要求技术具备更快的迭代速度。

    为了实现这样的速度,就需要充分利用云的强大能力,从云技术中获得更高的可用性与可扩展能力,利用云来提升发布和运维的效率。而要做到这些,不仅仅是基础设施和平台的变化,应用也需要做出改变,摈弃传统的土方法,在架构设计、开发方式、部署维护等各个阶段和方面都基于云的特点来重新设计,从而建设全新的云化应用,即云原生应用。

    image.png

    2019 年,Gartner 曾经发布报告表示云原生时代已经到来,在未来三年中将有 75%的全球化企业将在生产中使用容器化的应用。云原生相关技术不仅仅能用于云计算,即便是和云计算既对立又协同的边缘计算,微服务、容器、Kubernetes 依然是事实上的杀手应用和标准。

    2019 年,Gartner 曾经发布报告表示云原生时代已经到来,在未来三年中将有 75%的全球化企业将在生产中使用容器化的应用。云原生相关技术不仅仅能用于云计算,即便是和云计算既对立又协同的边缘计算,微服务、容器、Kubernetes 依然是事实上的杀手应用和标准。

    以前一家企业想使用云原生的技术或产品,需要花费大量的精力研究一些开源项目,自己做运维和管理,还需要考虑集成、稳定性保障等问题,这样才能建立一个云原生平台。今天,为了方便企业和开发者更容易地使用云原生的技术和产品,更好地接受云原生的理念,并解决企业担忧的可靠性、性能、连续性等问题,阿里云为大家提供了一整套云原生产品家族,提供了非常强的 SLA 保障。

    阿里云在帮助国内企业了解云原生、使用云原生上做了很多工作。一方面是在内部尝试去使用这些技术,阿里巴巴内部有非常丰富的、大规模的使用场景,通过这些场景可以充分打磨云原生技术。在技术成熟以后,将这些技术回馈到社区,帮助云原生社区提高技术质量和发展水平。

    3、因为相信,所以看见

    着云计算的普及与云原生的广泛应用,越来越多的从业者、决策者清晰地认识到「云原生化将成为企业技术创新的关键要素,也是完成企业数字化转型的最短路径」。因此,具有前瞻思维的互联网企业从应用诞生之初就扎根于云端,谨慎的新零售、政府、金融、医疗等领域的企业与机构也逐渐将业务应用迁移上云,深度使用云原生技术与云原生架构。

    畅捷通是中国领先的小型微型企业管理云服务与软件提供商,为400多万小微企业提供智能云管理服务。随着业务的快速发展,为了适应互联网大型应用快速迭代以及频繁发布的需求,畅捷通IT团队对原有的IT系统进行了大量的微服务化改造,这是畅捷通进行云原生实践迈出的第一步。

    紧接着,畅捷通开始迎接下一步挑战:SaaS化企业管理云服务,具有用户量大、业务复杂、调用链路长、与第三方应用系统深度集成等特点,给微服务化改造工作带来了非常大的挑战。特别是在新版本的发布过程中,如果不能保证整个流程平滑、可控,就很容易因为单个应用的更新而造成整个系统的崩溃。

    为了快速解决这个痛点,畅捷通IT团队找到阿里云技术专家,选择阿里,不仅因为阿里的业务场景复杂度和对技术打磨的细致,还有阿里一直以来在云原生领域的深耕。最终,畅捷通决定将整个微服务架构逐步部署到阿里云提供的企业级分布式应用服务(EDAS)上。通过Spring Cloud技术体系建立的微服务应用,可以在不涉及任何代码改动情况下,直接部署在EDAS上,整个迁移的过程也非常平滑,对于畅捷通的用户而言没有任何感知。部署之后,在面对复杂业务下的频繁迭代时,畅捷通成功经受住了考验。

    在进一步适应云原生的技术和产品以后,畅捷通的IT团队也通过方案背后融入的方法论,掌握了一套适合自己的微服务治理机制,并开始实践全链路灰度等全新的微服务治理思路。

    4、还能再极致一点吗?能!

    和大部分计算模式不同,Serverless 将“部署”这个动作从运维中“收走”,使开发者不用关心应用在哪里运行,更不用关心装什么 OS、怎么配置网络、需要多少 CPU …… 从架构抽象上看,当业务流量到来 / 业务事件发生时,云会启动或调度一个已启动的业务进程进行处理,处理完成后云自动会关闭/ 调度业务进程,等待下一次触发,也就是把应用的整个运行时都委托给云。

    Serverless 非常适合于事件驱动的数据计算任务、计算时间短的请求 / 响应应用、没有复杂相互调用的长周期任务。

    百富旅行是全球领先的在线旅游同业交易平台,基于云计算和大数据决策为全球旅游从业者提供一站式智能整体解决方案。截至目前,百富旅行已经与600多家航空公司、国内所有火车线路、2500个汽车站,以及60多家邮轮集团进行了业务整合,网络覆盖全球100多个国家和地区。

    随着业务的飞速发展,百富旅行技术团队通过Spring Cloud 等开源框架搭建了完善的微服务技术架构,将微服务应用上云之后,不需要再考虑硬件资源购买以及服务器架设等运维步骤,这样技术团队可以将更多的精力投入到业务需求实现中。但是随着系统迭代次数的增加,问题出现了:

    从系统架构的角度,将微服务应用直接部署在云虚拟机上,跟部署在物理机房相比,并没有本质的区别,团队依然需要从底层维护每一个应用实例,包括操作系统调整、磁盘容量规划、JDK等组件安装等工作,这些工作都每一台云虚拟机投入使用的过程中,都是必不可少的。

    在系统频繁的迭代过程中,不同的开发小组甚至不同的开发人员都需要单独的一套测试环境,久而久之,整个技术团队创建了多套测试环境,其中一些测试环境包含了所有的微服务应用,整体资源利用率特别低,造成了大量的资源浪费。而且旅游业务本身也存在非常明显的波峰波谷,微服务架构可以很方便地为每一个应用进行水平扩容,但如果用于扩容的虚拟机资源需要预先购买的话,同样会造成大量的资源闲置。

    摆在百富技术团队面前的核心难题就是怎么解决资源的闲置与浪费问题。而这恰恰是Serverless最擅长的事情。由于不需要为Serverlesss应用购买底层服务器资源,直接按需申请,可以免去容量规划、操作系统调优等复杂的运维工作,Serverless架构的弹性伸缩机制正好切断了资源浪费问题的源头。

    但是Serverless架构要怎么选,百富旅行技术团队开始对市场上常见的实现方式和产品做调研:

    Serverless架构有两种常见的实现方式:第一种是把每个微服务应用进行容器化改造后,统一使用Kubernetes进行编排,并利用云厂商提供的弹性容器实例实现容器层的按需调用。这种方式的门槛很高,需要有精通Kubernetes技术的运维小组加入,并且需要团队投入比较大的精力对应用进行容器化改造,暂时不适用于百富旅行这样小规模高效率的技术团队。

    另一种方式是使用类似于AWS Lambda或阿里云FC函数计算引擎,将所有业务逻辑进行函数化重构。这种方式基本上需要将之前写的代码推倒重来,而且在一些拥有复杂调用链路的业余环节并不能发挥Serverless的优势,最终排除在考虑范围内。

    经过多轮技术调研以及与阿里云技术专家深入交流后,百富旅行技术团队选择了阿里云Serverless应用引擎(SAE)方案。区别于其它 Serverless 产品,SAE 直接支持 Spring Cloud、Dubbo 等开发框架,实现了全面的Serverless化。开发者可以通过WAR、JAR、镜像三种方式部署Serverless应用,不需要学习Kubernetes以及容器技术。部署在SAE上的微服务应用,可以按需申请资源,根据实际使用资源量按分钟计费,避免业务不活跃时段的费用支出。特别是对于测试环境,SAE可以做到一键启停,避免了资源闲置问题。选择SAE为百富旅行节省了大量云资源的成本投入,并且减少一半以上的运维工作,为后续创新业务的发展打下了基础。

    5、云原生是一个时代下践行者们“前赴后继”的故事

    一项新技术或者一套新的技术理念,之所以能被广泛接受和快速发展,是因为有愿意相信并真正去落地实践的公司,是他们在为整个时代探索着云计算更大的技术价值。当五年、十年以后我们再来看洋葱学院、畅捷通、百富旅行等企业的实践历程,正是因为这些创新者们愿意接纳一些新的改变,并以此去撬动更大的想象空间,我们才经历了一个如此蓬勃和充满可能的时代。

    他们才是真的乘风破浪!

    ]]>
    企业“上云”开始起飞了! Fri, 20 Jun 2025 02:20:33 +0800 近日,阿里云正式启动《云原生合作计划》,旨在帮助合作伙伴实现云原生技术升级,合力加速百行千业实现数字化转型。知鱼科技光荣获封“云原生合作伙伴”称号,阿里云原生生态运营专家紫泷莅临知鱼科技,为我司正式授牌。
    image.png

    阿里云原生生态运营专家紫泷为知鱼科技授牌

    阿里云是全球领先的云计算及人工智能科技公司,为超过200个国家或地区的企业、开发者和政府机构提供服务。阿里云致力于以在线公共服务的方式,提供安全、可靠的计算和数据处理能力,让计算和人工智能成为普惠科技。2017年1月,阿里云成为奥运会全球指定云服务商。

    随着与阿里的合作深入,知鱼近年来通过跨界融合、全球整合,联合阿里云等第三方云生态资源产业,为企业数字化、产业升级转型打造一体化产品及服务,为客户提供专业、可靠的一站式IT服务。此次获封“云原生合作伙伴”称号,是知鱼在“专业云计算服务提供商”成长道路上的又一个里程碑。

    重大升级

    1、产品范围升级

    在云原生技术浪潮之下,阿里云原生合作伙伴计划在技术先进性和产品定位上做了重大升级,从中间件技术向云原生技术的转变,覆盖容器、微服务、Serverless、函数计算等众多产品。

    2、合作权益升级

    今年阿里云生态隆重推出“云聚计划”,投入20亿人民币,打造产品、技术、商务、交付的生态体系。

    作为《云原生合作计划》华南地区的核心合作企业,知鱼受邀参加阿里云华南生态FY21财年Q2启动会,依托自身优秀的综合性云产品服务,获得阿里云产品事业部颁发的“创新突破奖”。

    image.png
    image.png
    未来,知鱼将继续秉承合作共赢的积极态度,进一步携手以阿里云为代表的行业尖端合作伙伴,提供稳定可靠、安全可信、可持续演进的一站式云计算服务。

    ]]>
    在线教育,不可不知的阿里云中间件行业实战秘笈 Fri, 20 Jun 2025 02:20:33 +0800 “停课不停学”,疫情之下,几乎所有教育机构或平台都趁热打铁,上百项公益课程纷至沓来,令人眼花缭乱。据统计,从年初至今,13家在线教育相关公司的市值已经累计上涨近800亿元。线下教育停滞,巨额流量瞬间涌入线上。此次疫情对于在线教育的快速普及起到很大刺激作用,主要地区普及率将从目前的不到20%快速提升到接近100%,并将在线教育的学习方式推广进三四线城市。

    从在线教育长远的发展来讲,根据艾媒咨询的数据,2018年中国在线教育用户规模超过2亿人,而到2020年,有望达到3亿人。根据艾瑞咨询的数据,2018年中国在线教育市场规模2518亿元,预计在2022年市场规模将超过5000亿元。

    在线教育基于用户,产品,服务在线突破时间和空间的限制,打破跨越因地域等方面造成的教育资源不平等分配,使教育资源共享化,降低了学习的门槛。在线教育通过技术手段和能力,逐步实现内容供应-平台-分发推广平台化的生态能力。从在线教育的商业逻辑出发,其核心环节包括两个节点:流量的引入、流量的变现。

    在流量快速涌入的背景下,对在线教育机构及第三方服务提供商既是机遇,亦是挑战。挑战主要在:
    1、在流量快速注入的背景下,企业面对的挑战包括师资的招聘(尤其是疫情期间)、用户爆发下的服务维持和系统的稳定性能力。
    2、如何应对不确定性,“黑天鹅”现象频发,如何通过IT技术应对突发流量,徒增的业务以及不确定性的系统容量需求,从而在机会中能多的先机。

    从流量引入以及构建在线教育平台的核心竞争力来讲,挑战在于:
    1、新的产品,内容以及新的业务快速落地和试点能力,互联网时代唯快不破,新的业务如何快速试点不断迭代,从而吸引更多的用户,是技术赋能业务需要快速解决的问题。
    2、新业务上线以后,如何能引爆整个市场,服务于更多的用户。
    3、当服务于一定数量级用户后,如何精准和个性化的服务每一个客户。

    网络协同和数据智能是未来商业的基本特征,通过业务在线,用户在线以及服务在线,结合云的技术以及数据能力,实现在线教育客户运营效率提升,业务创新能力以及服务客户精准性,这些商业的能力依赖于IT基础设施云化,技术互联网化以及应用数据化和智能化。

    云原生基于开放的技术标准、理念和实践,正成为云和企业交互的新界面。阿里云中间件作为云原生的核心支点,其产品和最佳实践正在加速在线教育IT基础设施云化和技术互联网化。当前,历经阿里巴巴各种业务中打磨和验证的中间件,正以开放稳定的绝对优势,在阿里云在线教育等各个行业客户中广泛使用。

    此处通过一篇案例进行详细剖析:
    Timing App的Serverless实践案例
    1、背景和挑战:
    作为广受好评的学习应用,Timing App 专注于帮助社区用户提升学习凝聚力,达成学习目标。目前已有超过 700 万人通过 Timing 进行高效学习。与传统在线学习应用不同,Timing app 提供了 Timing 自习室、图书馆学习、 视频打卡、学习日记、契约群、学习服务等多类具有社交性质的在线教育服务,帮助用户找到自己的学习节奏,找到 坚持学习的一万种理由。Timing 业务本身具有潮汐特性,用户访问主要集中在晚间和节假日。受疫情影响,春节期 间峰值流量暴增 4 倍,公司面临较大的运维成本压力。在用户、流量爆发式增长背景下,Timing App 不得不直面以 下四大痛点:

    (1)系统稳定性差。原有 PHP 单应用架构系统无法做到线性快速扩容,在业务高峰时段,系统问题频繁发生,严 重影响用户体验。
    (2)产品迭代缓慢。随着业务的高速发展,原有单体架构对于产品的迭代力不从心,没法快速响应研发需求。
    (3)资源使用浪费。由于业务具有非常强的流量潮夕特征,需要按照业务高峰阶段进行资源保有配置,造成资源 的浪费。
    (4)技术成本昂贵。以前的团队除了技术负责人及少数团队新成员外,基本缺乏微服务架构实战经验。想要实现 微服务改造,急需能够快速上手的平台支撑,需要最大限度降低底层 IaaS, 容器以及常用微服务套件的学习 成本。

    2、云原生解决方案:
    阿里云应用引擎 Serverless(SAE),基于 Serverless 架构,屏蔽了底层 IaaS 运维和 K8s 细节,区别 于 FaaS 形态的 Serverless 产品,用户无需修改编程模型,零代码改造就能直接使用。同时,完美结合 Spring Cloud/Duboo 等微服务架构,提供应用发布、管理和服务治理等应用全生命周期的服务,完美贴合 Timing 的技术 需求:极限弹性伸缩,应用生命周期灵活管理,完美支持主流微服务架构。
    下图是方案架构示意图。
    image.png

    3、方案的关键优势:
    (1)利用弹性伸缩,应对不确定突发流量。提供秒级自动弹性 & 定时弹性能力,帮助应用轻松应对大促峰值流量, 保证 SLA 的同时也节省机器保有成本。多适用于互联网、游戏、在线教育行业。 应用环境随需灵活启停,节约成本。提供了一键启停开发测试环境的能力,即开即用,节省成本,方便运维。 适用于对成本敏感、云上有多套环境但部分环境闲置率较高的企业型客户(不限行业)。
    (2)中小企业快速构建云上微服务应用。帮助用户屏蔽底层 IaaS 购买和运维细节、底层 K8s 细节,低门槛部署 微服务应用。适用于初创型 / 上升期的公司(不限行业),业务增长很快,对增长有较高预期,但人员配置跟不上。
    (3)整体技术架构更为清晰,每个服务相互独立且职责明确。加之阿里云应用引擎 Serverless (SAE)加持, 让客户只关注在业务层,做好产品。

    【填问卷抽礼品】

    为了更好地给您提供定制化服务,诚邀您填写问卷,我们将随机抽取用户赠送淘宝纪念版公仔或指尖陀螺哦!
    【点击链接马上填问卷抽礼品】
    https://survey.aliyun.com/apps/zhiliao/YmW95Gk8bU
    image.png

    【加入客户交流钉钉群】

    欢迎扫码加入在线教育客户交流钉钉群,阿里巴巴众多专家将在群内定期分享行业最佳实践和前沿技术干货,扫码入群,与更多行业精英互动交流。
    image.png

    ]]>
    阿里云荣获可信云容器安全能力先进级认证, ACK/ACR为企业级安全护航 Fri, 20 Jun 2025 02:20:33 +0800 jnocvbcs.jpg
    阿里云关注企业级用户的Kubernetes生产落地痛点,结合企业生产环境的大量实践,全面帮助企业真正落地云原生架构。安全侧问题,是众多大中型或金融领域企业的核心关注点。

    端到端云原生安全架构

    早在2018年,阿里云容器服务团队率先提出了“端到端的企业级安全能力”概念,并推出立体式的端到端云原生安全架构。
    容器和云原生时代的安全挑战和传统安全主要有以下三点不同:

    • 第一个是高动态和高密度。传统时代一台机器只跑几个应用,而现在在一台服务器会运行上百个应用,是原来十几倍的密度。另外考虑到容器的自动恢复等特性,上一刻的容器在A机器,下一刻就会随时漂移到另一台机器。
    • 第二个是敏捷和快速迭代,容器 DevOps 化的应用发布非常频繁,是传统的几倍。
    • 第三,在开放标准、软件行业社会化大分工的时代,越来越多不可信三方开源软件的引入也加剧了安全风险。而容器的这些特点都会对云原生安全提出了更高的要求。
      为了应对这些安全风险,阿里云容器服务团队推出立体式的端到端云原生安全架构,并从三个层面来解决安全问题:

    image.png

    • 最底层依托于阿里云平台已有的安全能力,包括物理安全,硬件安全,虚拟化安全和云产品安全能力;
    • 中间是容器基础设施安全层,基于最小化攻击面原则,提供了包括访问控制,权限收敛,配置加固和身份管理等重要的底座安全能力;同时针对凭证下发,证书、密钥,集群审计等用户访问链路上的安全要素,构建了对应的自动化运维体系;
    • 在容器基础设施安全层之上,针对容器应用从构建到运行的生命周期在供应链和运行时刻提供对应的安全能力,比如在构建阶段提供了镜像安全扫描和镜像签名能力;在部署和运行时刻,提供了集运行时策略管理,配置巡检,运行时安全扫描的一体化安全管理能力,同时支持安全沙箱容器和TEE机密计算技术,为企业容器应用提供更好的安全隔离性和数据安全私密性。

    纵深防御,呵护容器应用全生命周期

    随着云原生技术的日趋火热,已经有越来越多的企业选择在自己的生产环境中进行容器化的云原生改造,而K8s社区的火热使得其成为众多舆论媒体关注的目标之外,也使得其成为众多攻击者攻击的主要目标。

    容器安全如今充满新挑战, 一方面是Kubernetes、helm、etcd等开源项目的高危漏洞频出,相关舆论愈发引起关注,据统计,从2018年开始,Kubernetes社区已经暴露了20余次CVE漏洞。

    另一方面,Kubernetes作为云原生时代新的操作系统与不同的异构计算设备的广泛集成以及serverless技术的日趋发展也使得容器应用的生命周期越来越短,同时集群节点的容器应用部署密度也越来越高,传统的供应链侧的安全扫描已经很难将风险完全暴露, 面对上述种种安全挑战,需要针对云原生下容器技术的特点,在安全上构建更加明确的防护体系和相应的技术升级。

    阿里云容器服务ACK和容器镜像服务ACR在上述的基础架构-软件供应链-运行时三层云安全架构基础上,还做了两大工作:纵深防御,构建从供应链到运行时的一体化安全流程;最小化攻击面,打造安全稳定的容器基础平台。

    在企业级用户的应用生命周期中,基于阿里云容器服务安全的整体架构,首先在应用的开发,测试和构建阶段,用户可以在供应链侧通过镜像安全扫描提前暴露应用镜像中的安全风险,同时企业级用户可以在阿里云容器镜像服务企业版 ACR EE 中开启指定仓库的镜像签名能力为推送镜像自动签名;在应用部署前,默认安全是应用系统中安全设计的重要原则,而配置安全也是容器应用在生产环境命令的主要风险。为此集群的安全管理员可以通过阿里云容器服务提供的统一策略管理平台,遵循一致性的规则配置定义,为不同集群内的应用系统提供定制化的安全治理性;在应用成功部署后,并不意味着我们的安全工作就到此结束了,用户可以通过容器服务安全管理中心提供的运行时监控告警、配置巡检、集群审计和密钥加密等手段,保护容器应用的运行时刻安全,构建整个容器安全的纵深防御能力。
    image.png

    客户的选择,业界的认可,阿里云的使命

    自2011年开始容器化进程,阿里开启了中国公司将云原生技术体系在电商、金融、制造等领域中大规模应用的先河,阿里云沉淀了最丰富的云原生产品家族、最全面的云原生开源贡献、最大的容器集群和客户群体和广泛的云原生应用实践。
    很多选择了阿里云容器服务的客户也会有各种各样的安全场景需求:

    某国际新零售巨头在意公司内部IT资产安全,使用容器镜像服务 ACR 安全软件供应链的镜像加签和扫描,RAM角色进行系列度RBAC权限控制,服务网格全链路mTLS认证、证书管理和审计。

    某国际金融银行关注数据运转安全 ,不仅使用基于阿里云容器服务ACK的全链路数据加密和云安全中心运行时刻告警监控,而且还搭配使用托管服务网格ASM进行应用东西向流量的细粒度控制。

    某国际游戏厂商希望更高效管控各方权限,进行了pod级别的控制层面云资源的权限隔离,外部KMS系统的密钥同步导入更新,数据平面系列度的权限控制,以及密钥管理确保容器敏感信息不会泄露。

    2020年5月, Gartner发布《Solution Comparison for the Native Security Capabilities 》报告,首次全面评估全球TOP云厂商的整体安全能力。阿里云作为亚洲唯一入围厂商,其整体安全能力拿下全球第二,11项安全能力被评估为最高水平(High)。

    ]]>
    全部满分!阿里云函数计算通过可信云21项测试 Fri, 20 Jun 2025 02:20:33 +0800 今日,“2020 可信云线上峰会”正式召开。会上,中国信通院公布了混合云安全、云组网、函数即服务、消息队列、云计算安全运营中心等首次评估结果。阿里云函数计算通过了基础能力要求、平台可观测能力、服务性能、服务安全和服务计量准确性等 21 项测试,最终以满分成绩通过可信云函数即服务能力认证。

    阿里云的函数计算(Function Compute)是一种 Serverless 计算形态,采用云原生架构模式,从底层开始变革计算资源的形态,为软件架构设计与应用服务部署带来了新的设计思路,将繁重的基础设施管理工作交由云服务商负责,从而提高开发者的研发效率和创新能力,被 Gartner 称为最有潜力的云计算技术发展方向。

    使用阿里云函数计算的优势:

    1、开发者无需采购和管理服务器等基础设施,只需专注业务逻辑的开发,可以大幅缩短项目交付时间和人力成本;
    2、函数计算提供日志查询、性能监控等完备的可观测性能力,帮助开发者快速排查故障;
    3、开发者无需投入精力到繁琐的运维工作,函数计算根据业务规模毫秒级别弹性伸缩,快速扩容以应对峰值压力,性能优异;

    2019年,阿里云推出函数计算 2.0,通过一系列创新的功能,解决了 Serverless 计算服务的痛点。在函数计算出现之前,客户要通过很多胶水代码完成多个云产品间的集成,还要仔细处理各种错误情况。当函数计算和阿里云对象存储集成后,对象存储中产生的上传 / 删除对象等事件能够自动、可靠地触发函数处理,而且每个环节都是弹性高可用的,用户能够快速实现大规模数据的实时并行处理。同样的,通过消息中间件和函数计算的集成,客户可以快速实现大规模消息的实时处理。在未来,无论是一方云服务,还是三方应用,所有事件都可被函数计算等服务可靠地处理。

    更重要的是,函数计算 2.0 新增预留实例类型,允许用户自行管理实例的申请和释放。通过预留实例,用户能够提前预热函数或者长期保持常驻实例,杜绝因为实例启动带来的请求延迟。当负载超过预留实例处理能力,系统会自动扩容,使用按量实例处理请求。同时函数计算提供了详细的实例使用指标,帮助用户轻松预留合理数目的实例。

    函数计算 2.0 大幅增强了 Serverless 应用构建、运维等方面的用户体验。用户可以在自己的开发机本地环境中创建和云端运行环境一致的沙盒,进行依赖包安装、断点调试等操作。函数计算2.0 也提供了 VSCode、Intellij Idea、PyCharm 等流行开发工具的插件,通过图形用户界面的交互方式,进一步降低了工具的使用门槛。

    阿里云函数计算有丰富的应用场景。以新浪微博为例,明星事件、红包飞等业务经常会遇到高达几倍的瞬间峰值。同时,微博也具有明显的流量潮汐效应,峰谷值相差 5 倍以上。微博采用阿里云函数计算,根据请求量动态分配执行环境,毫秒级调度计算资源,确保在负载高时保持稳定的延时,在负载低时有着较高的资源利用率,且只会对代码运行时使用的计算资源付费。更棒的是函数计算与对象存储服务无缝集成,可以方便地对存储在对象存储中的图片进行实时处理。

    新浪.png

    函数计算毫秒级伸缩计算资源确保应用在热点事件发生时仍能保证稳定的延时,用户体验完全不受访问次数的影响。函数计算可以自动弹性地分配更多执行环境以支撑微博业务的持续发展。

    函数计算免运维特性可以提高上线迭代效率,降低运维成本。工程师只需编写业务代码即可快速搭建云原生应用。

    以石墨文档为例,为了支持实时协作编写,技术团队在背后做了非常多的努力,同时也面临着更严峻的挑战。多用户的实时修改会对服务器带来不小的压力。 用户敲击键盘输入一个文字只需要几毫秒。而在石墨文档上同时编写文档的用户非常多,很容易出现在一个很小的时间段内数据分布不均的情况。

    石墨文档使用阿里云函数计算搭建文档实时编辑服务,将文档实时协作的逻辑实现为函数,由函数计算的智能调度系统自动分配执行环境,处理多用户同时编写文档带来的峰值负载。 借助函数计算毫秒级别的资源伸缩能力,石墨文档解决了早晚高峰负载突增的计算资源扩容问题,相比于自建机房维护服务器,提高了资源利用率,减少了闲置资源的浪费,节省了 58% 的服务器成本。由于不用再考虑 CPU 密集型计算的负载均衡问题,项目的迭代与上线的步伐逐渐快了起来,工程师集中精力与产品团队合作,不断扩展业务价值。 计算的负载均衡问题,大大提高了开发效率和进程稳定性。

    目前,阿里云上Serverless 产品除了函数计算(FC),还有 Serverles应用引擎(SAE)和 Serverless 工作流(Serverless WorkFlow)。SAE 是面向应用的 Serverless PaaS 平台,它向上抽象了应用的概念,支持 Spring Cloud、Dubbo、HSF 等流行的开发框架,并通过 WAR 包、JAR 包和镜像等多种方式部署应用。Serverless 工作流是一个用来协调多个分布式任务执行的全托管 Serverless 云服务,致力于简化开发和运行业务流程所需要的任务协调、状态管理以及错误处理等繁琐工作,让用户聚焦业务逻辑开发。

    阿里云函数计算广泛应用于 Web 应用、实时数据处理、AI 推理、视频转码等场景中。在这次疫情中,函数计算更是应用在数字抗疫中,助力 20万+ 企业远程复工。未来,阿里云会进一步加速推动基础设施和服务 Serverless 化,Serverless 会站在云计算的浪潮之巅,引领新一轮的技术升级。

    点击 函数计算 了解更多应用场景和案例实践。

    ]]>
    【升级】7月28日MMX注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【MMX注册局维护通知】

    维护时间:北京时间 2020年7月28日 13:00 - 15:00

    维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

    维护影响:届时 .Vip/.Work/.Beer/.Luxe/.Fit/.Yoga 域名的注册、续费、信息修改等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】7月30日阿里云域名注册、管理系统维护公告 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【域名注册、管理系统维护】

    维护时间:北京时间2020年7月30日 00:00 - 03:00

    维护内容:阿里云域名注册、管理系统将于上述时间进行系统维护。

    维护影响:届时域名注册、续费、转移、信息修改和域名信息模板、邮箱验证等相关功能,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的域名注册、续费、转入、赎回等操作,在支付费用后状态为“处理中”,待维护结束后将变为相应执行结果的正常状态;

    2、您提交的域名持有者过户、域名信息修改等操作将处理延迟,待维护结束后将变为相应执行结果的正常状态;

    3、您将无法对域名信息模板进行创建、修改、删除、设置为默认模板、提交模板实名认证等操作;

    4、您将不能提交域名邮箱验证和删除已验证邮箱。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月4日Donuts注册局维护通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【域名】【Donuts注册局维护通知】

    维护时间:北京时间2020年8月4日 01:00 - 02:30

    维护内容:接到注册局的通知,注册局 / 注册商将于上述时间对后台系统进行维护升级。

    维护影响:届时,您的 .ltd/.group/.pub/.live/.rocks/.band/.market/.software/.social/.lawyer/.engineer/.news/.video/.studio/.today /.plus/.world/.run/.show/.city/.gold/.today/.cool 等域名的续费、转入转出、信息修改和过户域名等操作,将会无法使用,在此期间会对您造成的影响如下:

    1、您提交的续费、转入、转出域名等操作在支付费用后状态为“处理中”,且可能出现“不成功”等状态;

    2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

    如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

    如您的业务操作失败,建议维护后再次尝试。

    由此给您带来的不便,我们表示歉意,敬请谅解。

    ]]>
    【升级】8月消息队列MQ升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【消息队列MQ】【升级通知】

    升级窗口:

    北京时间2020年08月04日 01:00 - 06:00

    北京时间2020年08月06日 01:00 - 06:00

    北京时间2020年08月11日 01:00 - 06:00

    北京时间2020年08月13日 01:00 - 09:00

    北京时间2020年08月18日 01:00 - 09:00

    北京时间2020年08月20日 01:00 - 09:00

    北京时间2020年08月25日 01:00 - 09:00

    北京时间2020年08月27日 01:00 - 09:00
    升级内容:所有地域的MQ服务(包含Tcp、Mqtt、Http接入方式)。

    升级影响:升级期间MQ控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端会自动重试机制,一般不会影响业务,但可能会有异常日志。
    升级期间,消息可能会有重复,应用应该按最值实践,做好消息的幂等;同时可能会有消息延迟的现象。
    如需在控制台进行管理操作,请避开维护时间段。HTTP接入可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。同时,您可使用监控管理功能对重要业务进行监控,具体设置方法请点击MQ控制台左侧监控报警菜单。

    给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

    ]]>
    【漏洞预警】WebSphere远程代码执行漏洞(CVE-2020-4450) Fri, 20 Jun 2025 02:20:33 +0800

    近日,阿里云应急响应中心监测到国外某安全研究团队披露关于CVE-2020-4450 WebSphere 远程代码执行漏洞相关分析详情。


    漏洞描述

    WebSphere Application Server 是一款由IBM 公司开发的高性能的 Java 应用服务器,可用于构建、运行、集成、保护和管理内部部署和/或外部部署的动态云和 Web 应用。2020年6月5日IBM官方发布安全补丁,修复了CVE-2020-4450 WebSphere Application Server 远程代码执行漏洞。2020年7月20日国外某安全团队披露相关漏洞分析,未经身份验证的远程攻击者可以通过IIOP协议,构造恶意请求从而在WebSphere实现远程代码执行。阿里云应急响应中心提醒 WebSphere 用户尽快采取安全措施阻止漏洞攻击


    影响版本

    WebSphere Application Server 9.0.0.0 – 9.0.5.4

    WebSphere Application Server 8.5.0.0 – 8.5.5.17

    WebSphere Application Server 8.0.0.0 – 8.0.0.15

    WebSphere Application Server 7.0.0.0 – 7.0.0.45


    安全建议

    建议将 WebSphere Application Server 升级至安全版本。下载地址参考:https://www.ibm.com/support/pages/node/6220276


    相关链接

    https://www.zerodayinitiative.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere



    我们会关注后续进展,请随时关注官方公告。

    如有任何问题,可随时通过工单或服务电话95187联系反馈。

    阿里云应急响应中心

    2020.07.29

    ]]>
    Docker入门篇-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Docker简介

    Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口

    Docker优势

    持续集成、版本控制、可移植性、隔离性和安全

    Docker的组成

    Docker组成

    镜像(image):

    Docker镜像就相当于是一个文件系统,通俗来说就是为容器用来创建容器的

    容器(Container):

    Docker 利用容器 (Container) 独立运行的一个或一组应用,容器是用镜像创建的运行实例,它可以被启动、开始、停止、删除,每个容器都是相互隔离的、保证安全的平台,可以把容器看做是一个简易版的Linux系统

    容器就相当于Java中的对象,镜像相当于Java中的类

    仓库(Repository)

    仓库是集中存放镜像文件的场所
    仓库注册服务器 (Registry) 上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签
    仓库分为公开仓库和私有仓库两种形式
    最大的公开仓库是DockerHub存放了数量庞大的镜像供用户下载,国内的公开仓库包括阿里云等

    Docker的底层原理

    Docker的工作原理

    Docker是基于C/S结构的系统,Docker的守护进程运行在Linux服务器(宿主机)上,当我们在Liunx服务器上(Docker-Client)输入Docker相关命令就会被发送给Doker-Server

    为什么Docker比VM(虚拟机)快

    1、Docker有着比虚拟机更少的抽象层,由于Docker不需要Hypervisor实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源,因此在CPU、内存利用率上docker将会在效率上有明显优势
    2、当新建一个容器时,Docker不需要和虚拟机一样重新加载一个操作系统内核,因而避免引导、加载操作系统内核返个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载GuestOS(虚拟机中的操作系统),这个新建过程是分钟级别的,而Docker由于直接利用宿主机的操作系统,则省略了这个复杂的过程,因此新建一个Docker容器只需要几秒钟

    Docker的安装

    1.需要Liunx版本为CentOS7.0以上

    2.卸载旧版本(非必须项)

    yum remove docker 
                      docker-client 
                      docker-client-latest 
                      docker-common 
                      docker-latest 
                      docker-latest-logrotate 
                      docker-logrotate 
                      docker-engine

    3.安装Docker所需要依赖

     yum install -y yum-utils  device-mapper-persistent-data  lvm2

    4.设置镜像仓库

    yum-config-manager  --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

    5.安装Docker

    yum -y install docker-ce

    6.设置阿里云镜像加速

    阿里云镜像加速)

    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json <<-'EOF'
    {
      "registry-mirrors": ["https://qhyb8ixp.mirror.aliyuncs.com"]
    }
    EOF
    sudo systemctl daemon-reload
    sudo systemctl restart docker

    7.开启Dcoker服务

    service docker start

    8.测试Docker-HelloWorld程序

    [root@iZ8vbi9mx98t2s78lyxfpuZ ~]# docker run hello-world 
    Unable to find image 'hello-world:latest' locally
    docker run hello-worldlatest: Pulling from library/hello-world
    0e03bdcc26d7: Already exists
    Digest: sha256:6a65f928fb91fcfbc963f7aa6d57c8eeb426ad9a20c7ee045538ef34847f44f1
    Status: Downloaded newer image for hello-world:latest
    
    Hello from Docker!             --> 出现这句话代表Docker安装没有问题
    This message shows that your installation appears to be working correctly.

    9.卸载Docker

    #1. 卸载依赖
    yum remove docker-ce docker-ce-cli containerd.io
    #2. 删除资源 /var/lib/docker 是docker的默认工作路径!
    rm -rf /var/lib/docker
    

    Docker帮助命令

    1. docker version     # 显示Docker的版本信息
    2. docker info           # 显示Docker的系统信息(包含镜像与容器的数量)
    3. docker 命令 --help  # 查看帮助(就是显示这个命令有什么可选项)

    Docker镜像命令

    1. docker images:显示本机上所有安装镜像

      [root@MT ~]# docker images
      
      REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
      centos              latest              470671670cac        4 months ago        237MB
      hello-world         latest              bf756fb1ae65        4 months ago        13.3kB
      
      # 可选项
      docker images -a # 列出所有的镜像
      docker images -q # 只显示镜像的id
      
      [root@MT ~]# docker images -aq
      470671670cac
      bf756fb1ae65
    2. docker search:搜索镜像

      [root@MT ~]# docker search mysql(镜像名)
      
      NAME                              DESCRIPTION                                     STARS
      mysql                             MySQL is a widely used, open-source relation…   9553             
      mariadb                           MariaDB is a community-developed fork of MyS…   3471 
      
      # 可选项
      docker search mysql -f=STARS=3000 # 列出镜像星数大于3000的镜像
    3. docker pull:下载镜像

      [root@MT ~]# docker pull mysql      # 下载最新版本的MySQL
      [root@MT ~]# docker pull mysql:5.7  # 下载指定版本的MySQL
    4. docker rmi:删除镜像

      docker rmi -f 镜像id                      # 删除指定的镜像
      docker rmi -f 镜像id 镜像id 镜像id 镜像id  # 删除指定的镜像
      docker rmi -f $(docker images -aq)       # 删除全部的镜像

    Docker容器命令

    1. docker run [可选参数] 镜像名

      [root@MT ~]# docker run 可选参数 镜像名
      
      # 可选参数
      --name=”xxx“    # 容器名字
      -d              # 后台模式运行
      -it             # 使用交互模式运行,进入容器查看内容
      -p(小学)         # 指定端口映射 如:-p 8080(宿主机):8080(容器)
      -P(大写)         # 随机端口映射
      
      # 测试启动进入与退出容器
      [root@MT ~]# docker run -it centos /bin/bash           # /bin/bash 是使用命令行进入
      [root@6130f0353526 /]# ls                              # 此时已经进入Docker的CentOS容器
      bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
      [root@6130f0353526 /]# exit
      exit
      [root@MT ~]#
      
    2. docker ps [可选参数]:列出运行的容器

      [root@MT ~]# docker ps 可选参数
      
      # 可选参数
      -a       # 列出正在运行和历史运行过的容器
      -q       # 列出容器ID
      
      [root@MT ~]# docker ps -aq
      6130f0353526
      895577bc1ae7
      f9fc9c23267d
      faab3fceb1d3
    3. 退出容器

      exit          # 容器直接退出
      Ctrl + P + Q  # 容器不停止退出
    4. 删除容器

      docker rm -f 容器id              # 删除指定容器(不能删除正在运行的,如需要rm-rf)
      docker rm -f $(docker ps -aq)    # 删除所有容器
    5. 启动和停止容器操作

      docker start 容器id      # 启动容器
      docker restart 容器id    # 重启容器
      docker stop 容器id       # 停止正在运行的容器
      docker kill 容器id       # 强制停止

    Docker其它命令

    容器后台方式运行

    [root@MT ~]# docker run -d centos
    748d11ff226983ab8281fc6741087e6a3f349ad03f0cadafcd215aed894978dc
    [root@MT ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
     # 问题:我们此时已经发现启动CentOS,但是确查不到正在运行的容器
     # 解释:Docker容器使用后台运行,就必须要有要一个前台进程,Docker发现没有应用,就会自动停止

    查看日志

    docker logs -tf 容器id             # 查看实时日志  
    docker logs -t --tail 数量 容器id  # 查看指定数量的日志           

    查看容器中的进程信息

    docker top 容器id

    查看镜像的元数据

    docker inspect 容器id

    进入当前正在运行的容器

    # 通常我们是使用后台方式运行容器,因为我们可能需要进入容器,修改配置
    
    # docker exec -it 容器ID /bin/bash (常用)
    # docker attach 容器ID
    
    # exec方式:就是类似新开一个窗口连接运行的容器
    # attach方式:就是直接连入当前的容器

    拷贝容器内文件到宿主机中

    docker cp 容器id:文件路径(容器内) 目的路径(宿主机)
    [root@MT ~]docker cp 1a9a6785c37c:/home/c.java /home/

    Docker安装Nginx

    # 1. 搜索镜像 search 建议大家去docker搜索,可以看到帮助文档
    docker search nginx
    # 2. 拉取镜像 docker pull nginx
    docker pull nginx
    # 3. 运行测试
      # -d 后台运行
      # --name 给容器命名
      # -p 宿主机端口:容器内部端口(我这里使用阿里云的3344端口,前提要在阿里云安全组开放次端口)
    docker run -d --name nginx -p 3344:80 nginx
    # 4.查看镜像
    docker ps
    # 5.测试是否成功
    curl localhost:3344 # 或者在浏览器输入ip:3344

    Docker安装Tomcat

    # 下载tomcat镜像
    docker pull tomcat:9.0
    # 运行tomcat镜像
    docker run -d tomcat -p 3344:8080 tomcat
    # 测试
    curl localhost:3344  # 此时发现无法访问
    # 原因:tomcat容器里面webapps目录下没有文件
    # 进入容器
    docker exec -it ef41045bea2b /bin/bash
    cp -r webapps.dist/* webapps
    # 重新测试 在浏览器输入ip:3344 --> 发现成功访问
    ]]>
    解决问题 1474 个,Flink 1.11 究竟有哪些易用性上的改善?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 作者 | 王治江,Apache Flink PMC

    7月7日,Flink 1.11.0 正式发布了,作为这个版本的 release manager 之一,我想跟大家分享一下其中的经历感受以及一些代表性 feature 的解读。在进入深度解读前,我们先简单了解下社区发布的一般流程,帮助大家更好的理解和参与 Flink 社区的工作。

    • 首先在每个版本的规划初期,会从志愿者中选出 1-2 名作为 Release Manager。1.11.0 版本我作为中国这边的 Release Manager,同时还有一名来自 Ververica 的 Piotr Nowojski 作为德国方的 Release Manager,这在某种程度上也说明中国的开发者和贡献度在整个社区的占比很重要。
    • 接下来会进行这个版本的 Feature Kickoff。在一些大的方向上,社区的规划周期可能比较久,会分阶段、分步骤跨越多个版本完成,确保质量。每个版本的侧重点也会有所不同,比如前两个版本侧重于批处理的加强,而这个版本更侧重于流处理易用性的提升。社区规划的 Feature 列表会在邮件列表中发起讨论,以收集更多的用户/开发者意见和反馈。
    • 一般的开发周期为 2-3 个月时间,提前会明确规划出大概的 Feature Freeze 时间,之后进行 Release Candidate 的发布和测试、以及 Bug Fix。一般经过几轮的迭代周期后会正式投票通过一个相对稳定的 Candidate 版本,然后基于这个版本正式发布。

    Flink 1.11.0 从 3 月初的功能规划到 7 月初的正式发布,历经了差不多 4 个月的时间,对 Flink 的生态、易用性、生产可用性、稳定性等方面都进行了增强和改善,下面将一一跟大家分享。

    一 综述

    Flink 1.11.0 从 Feature 冻结后发布了 4 次 Candidate 才最终通过。经统计,一共有 236 个贡献者参与了这次版本开发,解决了 1474 个 Jira 问题,涉及 30 多个 FLIP,提交了 2325 个 Commit。

    1.jpg
    2.jpg

    纵观近五次版本发布,可以看出从 1.9.0 开始 Flink 进入了一个快速发展阶段,各个维度指标相比之前都有了几乎翻倍的提高。也是从 1.9.0 开始阿里巴巴内部的 Blink 项目开始被开源 Flink 整合,到 1.10.0 经过两个大版本已经全部整合完毕,对 Flink 从生态建设、功能性、性能和生产稳定性上都有了大幅的增强。

    Flink 1.11.0 版本的最初定位是重点解决易用性问题,提升用户业务的生产使用体验,整体上不做大的架构调整和功能开发,倾向于快速迭代的小版本开发。但是从上面统计的各个指标来看,所谓的“小版本”在各个维度的数据也丝毫不逊色于前两个大版本,解决问题的数量和参与的贡献者人数也在持续增加,其中来自中国的贡献者比例达到 62%。

    下面我们会深度剖析 Flink 1.11.0 带来了哪些让大家期待已久的特性,从用户直接使用的 API 层一直到执行引擎层,我们都会选择一些有代表性的 Feature 从不同维度解读,更完整的 Feature 列表请大家关注发布的 Release Blog。

    二 生态完善和易用性提升

    这两个维度在某种程度上是相辅相成的,很难严格区分开,生态兼容上的缺失常常造成使用上的不便,提升易用性的过程往往也是不断完善相关生态的过程。在这方面用户感知最明显的应该就是 Table & SQL API 层面的使用。

    1 Table & SQL 支持 Change Data Capture(CDC)

    CDC 被广泛使用在复制数据、更新缓存、微服务间同步数据、审计日志等场景,很多公司都在使用开源的 CDC 工具,如 MySQL CDC。通过 Flink 支持在 Table & SQL 中接入和解析 CDC 是一个强需求,在过往的很多讨论中都被提及过,可以帮助用户以实时的方式处理 Changelog 流,进一步扩展 Flink 的应用场景,例如把 MySQL 中的数据同步到 PG 或 ElasticSearch 中,低延时的 Temporal Join 一个 Changelog 等。

    除了考虑到上面的真实需求,Flink 中定义的“Dynamic Table”概念在流上有两种模型:Append 模式和 Update 模式。通过 Append 模式把流转化为“Dynamic Table”在之前的版本中已经支持,因此在 1.11.0 中进一步支持 Update 模式也从概念层面完整的实现了“Dynamic Table”。

    3.jpg

    为了支持解析和输出 Changelog,如何在外部系统和 Flink 系统之间编解码这些更新操作是首要解决的问题。考虑到 Source 和 Sink 是衔接外部系统的一个桥梁,因此 FLIP-95 在定义全新的 Table Source 和 Table Sink 接口时解决了这个问题。

    在公开的 CDC 调研报告中,Debezium 和 Canal 是用户中最流行使用的 CDC 工具,这两种工具用来同步 Changelog 到其它的系统中,如消息队列。据此,FLIP-105 首先支持了 Debezium 和 Canal 这两种格式,而且 Kafka Source 也已经可以支持解析上述格式并输出更新事件,在后续的版本中会进一步支持 Avro(Debezium) 和 Protobuf(Canal)。

    CREATE TABLE my_table (  
    ...) WITH (  
    'connector'='...', -- e.g. 'kafka'  
    'format'='debezium-json',  
    'debezium-json.schema-include'='true' -- default: false (Debezium can be configured to include or exclude the message schema)  
    'debezium-json.ignore-parse-errors'='true' -- default: false
    );

    2 Table & SQL 支持 JDBC Catalog

    1.11.0 之前,用户如果依赖 Flink 的 Source/Sink 读写关系型数据库或读取 Changelog 时,必须要手动创建对应的 Schema。而且当数据库中的 Schema 发生变化时,也需要手动更新对应的 Flink 作业以保持一致和类型匹配,任何不匹配都会造成运行时报错使作业失败。用户经常抱怨这个看似冗余且繁琐的流程,体验极差。

    实际上对于任何和 Flink 连接的外部系统都可能有类似的上述问题,在 1.11.0 中重点解决了和关系型数据库对接的这个问题。FLIP-93 提供了 JDBC catalog 的基础接口以及 Postgres catalog 的实现,这样方便后续实现与其它类型的关系型数据库的对接。

    1.11.0 版本后,用户使用 Flink SQL 时可以自动获取表的 Schema 而不再需要输入 DDL。除此之外,任何 Schema 不匹配的错误都会在编译阶段提前进行检查报错,避免了之前运行时报错造成的作业失败。这是提升易用性和用户体验的一个典型例子。

    3 Hive 实时数仓

    从 1.9.0 版本开始 Flink 从生态角度致力于集成 Hive,目标打造批流一体的 Hive 数仓。经过前两个版本的迭代,已经达到了 Batch 兼容且生产可用,在 TPC-DS 10T Benchmark 下性能达到 Hive 3.0 的 7 倍以上。

    1.11.0 在 Hive 生态中重点实现了实时数仓方案,改善了端到端流式 ETL 的用户体验,达到了批流一体 Hive 数仓的目标。同时在兼容性、性能、易用性方面也进一步进行了加强。

    在实时数仓的解决方案中,凭借 Flink 的流式处理优势做到实时读写 Hive:

    • Hive 写入:FLIP-115 完善扩展了 FileSystem Connector 的基础能力和实现,Table/SQL 层的 sink 可以支持各种格式(CSV、Json、Avro、Parquet、ORC),而且支持 Hive Table 的所有格式。
    • Partition 支持:数据导入 Hive 引入 Partition 提交机制来控制可见性,通过sink.partition-commit.trigger 控制 Partition 提交的时机,通过 sink.partition-commit.policy.kind 选择提交策略,支持 SUCCESS 文件和 Metastore 提交。
    • Hive 读取:实时化的流式读取 Hive,通过监控 Partition 生成增量读取新 Partition,或者监控文件夹内新文件生成来增量读取新文件。

    在 Hive 可用性方面的提升:

    • FLIP-123 通过 Hive Dialect 为用户提供语法兼容,这样用户无需在 Flink 和 Hive 的 CLI 之间切换,可以直接迁移 Hive 脚本到 Flink 中执行。
    • 提供 Hive 相关依赖的内置支持,避免用户自己下载所需的相关依赖。现在只需要单独下载一个包,配置 HADOOP_CLASSPATH 就可以运行。

    在 Hive 性能方面,1.10.0 中已经支持了 ORC(Hive 2+)的向量化读取,1.11.0 中我们补全了所有版本的 Parquet 和 ORC 向量化支持来提升性能。

    4 全新 Source API

    前面也提到过,Source 和 Sink 是 Flink 对接外部系统的一个桥梁,对于完善生态、可用性及端到端的用户体验是很重要的环节。社区早在一年前就已经规划了 Source 端的彻底重构,从 FLIP-27 的 ID 就可以看出是很早的一个 Feature。但是由于涉及到很多复杂的内部机制和考虑到各种 Source Connector 的实现,设计上需要考虑的很全面。从 1.10.0 就开始做 POC 的实现,最终赶上了 1.11.0 版本的发布。

    先简要回顾下 Source 之前的主要问题:

    • 对用户而言,在 Flink 中改造已有的 Source 或者重新实现一个生产级的 Source Connector 不是一件容易的事情,具体体现在没有公共的代码可以复用,而且需要理解很多 Flink 内部细节以及实现具体的 Event Time 分配、Watermark 产出、Idleness 监测、线程模型等。
    • 批和流的场景需要实现不同的 Source。
    • Partitions/Splits/Shards 概念在接口中没有显式表达,比如 Split 的发现逻辑和数据消费都耦合在 Source Sunction 的实现中,这样在实现 Kafka 或 Kinesis 类型的 Source 时增加了复杂性。
    • 在 Runtime 执行层,Checkpoint 锁被 Source Function 抢占会带来一系列问题,框架很难进行优化。

    FLIP-27 在设计时充分考虑了上述的痛点:

    4.jpg

    • 首先在 Job Manager 和 Task Manager 中分别引入两种不同的组件 Split Enumerator 和 Source Reader,解耦 Split 发现和对应的消费处理,同时方便随意组合不同的策略。比如现有的 Kafka Connector 中有多种不同的 Partition 发现策略和实现耦合在一起,在新的架构下,我们只需要实现一种 Source Reader,就可以适配多种 Split Enumerator 的实现来对应不同的 Partition 发现策略。
    • 在新架构下实现的 Source Connector 可以做到批流统一,唯一的小区别是对批场景的有限输入,Split Enumerator 会产出固定数量的 Split 集合并且每个 Split 都是有限数据集;对于流场景的无限输入,Split Enumerator 要么产出无限多的 Split 或者 Split 自身是无限数据集。
    • 复杂的 Timestamp Assigner 以及 Watermark Generator 透明的内置在 Source Reader 模块内运行,对用户来说是无感知的。这样用户如果想实现新的 Source Connector,一般不再需要重复实现这部分功能。

    目前 Flink 已有的 Source Connector 会在后续的版本中基于新架构来重新实现,Legacy Source 也会继续维护几个版本保持兼容性,用户也可以按照 Release 文档中的说明来尝试体验新 Source 的开发。

    5 PyFlink 生态

    众所周知,Python 语言在机器学习和数据分析领域有着广泛的使用。Flink 从 1.9.0 版本开始发力兼容 Python 生态,Python 和 Flink 合力为 PyFlink,把 Flink 的实时分布式处理能力输出给 Python 用户。前两个版本 PyFlink 已经支持了 Python Table API 和 UDF,在 1.11.0 中扩大对 Python 生态库 Pandas 的支持以及和 SQL DDL/Client 的集成,同时 Python UDF 性能有了极大的提升。

    具体来说,之前普通的 Python UDF 每次调用只能处理一条数据,而且在 Java 端和 Python 端都需要序列化/反序列化,开销很大。1.11.0 中 Flink 支持在 Table & SQL 作业中自定义和使用向量化 Python UDF,用户只需要在 UDF 修饰中额外增加一个参数 udf_type=“pandas” 即可。这样带来的好处是:

    • 每次调用可以处理 N 条数据。
    • 数据格式基于 Apache Arrow,大大降低了 Java、Python 进程之间的序列化/反序列化开销。
    • 方便 Python 用户基于 Numpy 和 Pandas 等数据分析领域常用的 Python 库,开发高性能的 Python UDF。

    除此之外,1.11.0 中 PyFlink 还支持:

    • PyFlink table 和 Pandas DataFrame 之间无缝切换(FLIP-120),增强 Pandas 生态的易用性和兼容性。
    • Table & SQL 中可以定义和使用 Python UDTF(FLINK-14500),不再必需 Java/Scala UDTF。
    • Cython 优化 Python UDF 的性能(FLIP-121),对比 1.10.0 可以提升 30 倍。
    • Python UDF 中用户自定义 Metric(FLIP-112),方便监控和调试 UDF 的执行。

    上述解读的都是侧重 API 层面,用户开发作业可以直接感知到的易用性的提升。下面我们看看执行引擎层在 1.11.0 中都有哪些值得关注的变化。

    三 生产可用性和稳定性提升

    1 支持 Application 模式和 Kubernetes 增强

    1.11.0 版本前,Flink 主要支持如下两种模式运行:

    • Session 模式:提前启动一个集群,所有作业都共享这个集群的资源运行。优势是避免每个作业单独启动集群带来的额外开销,缺点是隔离性稍差。如果一个作业把某个 Task Manager(TM)容器搞挂,会导致这个容器内的所有作业都跟着重启。虽然每个作业有自己独立的 Job Manager(JM)来管理,但是这些 JM 都运行在一个进程中,容易带来负载上的瓶颈。
    • Per-job 模式:为了解决 Session 模式隔离性差的问题,每个作业根据资源需求启动独立的集群,每个作业的 JM 也是运行在独立的进程中,负载相对小很多。

    以上两种模式的共同问题是需要在客户端执行用户代码,编译生成对应的 Job Graph 提交到集群运行。在这个过程需要下载相关 Jar 包并上传到集群,客户端和网络负载压力容易成为瓶颈,尤其当一个客户端被多个用户共享使用。

    1.11.0 中引入了 Application 模式(FLIP-85)来解决上述问题,按照 Application 粒度来启动一个集群,属于这个 Application 的所有 Job 在这个集群中运行。核心是 Job Graph 的生成以及作业的提交不在客户端执行,而是转移到 JM 端执行,这样网络下载上传的负载也会分散到集群中,不再有上述 Client 单点上的瓶颈。

    用户可以通过 bin/flink run-application 来使用 Application 模式,目前 Yarn 和 Kubernetes(K8s)都已经支持这种模式。Yarn application 会在客户端将运行作业需要的依赖都通过 Yarn Local Resource 传递到 JM。K8s Application 允许用户构建包含用户 Jar 与依赖的镜像,同时会根据作业自动创建 TM,并在结束后销毁整个集群,相比 Session 模式具有更好的隔离性。K8s 不再有严格意义上的 Per-Job 模式,Application 模式相当于 Per-Job 在集群进行提交作业的实现。

    除了支持 Application 模式,Flink 原生 K8s 在 1.11.0 中还完善了很多基础的功能特性(FLINK-14460),以达到生产可用性的标准。例如 Node Selector、Label、Annotation、Toleration 等。为了更方便的与 Hadoop 集成,也支持根据环境变量自动挂载 Hadoop 配置的功能。

    2 Checkpoint & Savepoint 优化

    Checkpoint 和 Savepoint 机制一直是 Flink 保持先进性的核心竞争力之一,社区在这个领域的改动很谨慎,最近的几个大版本中几乎没有大的功能和架构上的调整。在用户邮件列表中,我们经常能看到用户反馈和抱怨的相关问题:比如 Checkpoint 长时间做不出来失败,Savepoint 在作业重启后不可用等等。1.11.0 有选择的解决了一些这方面的常见问题,提高生产可用性和稳定性。

    1.11.0 之前, Savepoint 中 Meta 数据和 State 数据分别保存在两个不同的目录中,这样如果想迁移 State 目录很难识别这种映射关系,也可能导致目录被误删除,对于目录清理也同样有麻烦。1.11.0 把两部分数据整合到一个目录下,这样方便整体转移和复用。另外,之前 Meta 引用 State 采用的是绝对路径,这样 State 目录迁移后路径发生变化也不可用,1.11.0 把 State 引用改成了相对路径解决了这个问题(FLINK-5763),这样 Savepoint 的管理维护、复用更加灵活方便。

    实际生产环境中,用户经常遭遇 Checkpoint 超时失败、长时间不能完成带来的困扰。一旦作业 failover 会造成回放大量的历史数据,作业长时间没有进度,端到端的延迟增加。1.11.0 从不同维度对 Checkpoint 的优化和提速做了改进,目标实现分钟甚至秒级的轻量型 Checkpoint。

    首先,增加了 Checkpoint Coordinator 通知 Task 取消 Checkpoint 的机制(FLINK-8871),这样避免 Task 端还在执行已经取消的 Checkpoint 而对系统带来不必要的压力。同时 Task 端放弃已经取消的 Checkpoint,可以更快的参与执行 Coordinator 新触发的 Checkpoint,某种程度上也可以避免新 Checkpoint 再次执行超时而失败。这个优化也对后面默认开启 Local Recovery 提供了便利,Task 端可以及时清理失效 Checkpoint 的资源。

    其次,在反压场景下,整个数据链路堆积了大量 Buffer,导致 Checkpoint Barrier 排在数据 Buffer 后面,不能被 Task 及时处理对齐,也就导致了 Checkpoint 长时间不能执行。1.11.0 中从两个维度对这个问题进行解决:

    1)尝试减少数据链路中的 Buffer 总量(FLINK-16428),这样 Checkpoint Barrier 可以尽快被处理对齐。

    • 上游输出端控制单个 Sub Partition 堆积 Buffer 的最大阈值(Backlog),避免负载不均场景下单个链路上堆积大量 Buffer。
    • 在不影响网络吞吐性能的情况下合理修改上下游默认的 Buffer 配置。
    • 上下游数据传输的基础协议进行了调整,允许单个数据链路可以配置 0 个独占 Buffer 而不死锁,这样总的 Buffer 数量和作业并发规模解耦。根据实际需求在吞吐性能和 Checkpoint 速度两者之间权衡,自定义 Buffer 配比。

    这个优化有一部分工作已经在 1.11.0 中完成,剩余部分会在下个版本继续推进完成。

    2)实现了全新的 Unaligned Checkpoint 机制(FLIP-76)从根本上解决了反压场景下 Checkpoint Barrier 对齐的问题。实际上这个想法早在 1.10.0 版本之前就开始酝酿设计,由于涉及到很多模块的大改动,实现机制和线程模型也很复杂。我们实现了两种不同方案的原型 POC 进行了测试、性能对比,确定了最终的方案,因此直到 1.11.0 才完成了 MVP 版本,这也是 1.11.0 中执行引擎层唯一的一个重量级 Feature。其基本思想可以概括为:

    • Checkpoint Barrier 跨数据 Buffer 传输,不在输入输出队列排队等待处理,这样就和算子的计算能力解耦,Barrier 在节点之间的传输只有网络延时,可以忽略不计。
    • 每个算子多个输入链路之间不需要等待 Barrier 对齐来执行 Checkpoint,第一个到的 Barrier 就可以提前触发 Checkpoint,这样可以进一步提速 Checkpoint,不会因为个别链路的延迟而影响整体。
    • 为了和之前 Aligned Checkpoint 的语义保持一致,所有未被处理的输入输出数据 Buffer 都将作为 Channel State 在 Checkpoint 执行时进行快照持久化,在 Failover 时连同 Operator State 一同进行恢复。换句话说,Aligned 机制保证的是 Barrier 前面所有数据必须被处理完,状态实时体现到 Operator State 中;而 Unaligned 机制把 Barrier 前面的未处理数据所反映的 Operator State 延后到 Failover Restart 时通过 Channel State 回放进行体现,从状态恢复的角度来说最终都是一致的。注意这里虽然引入了额外的 In-Flight Buffer 的持久化,但是这个过程实际是在 Checkpoint 的异步阶段完成的,同步阶段只是进行了轻量级的 Buffer 引用,所以不会过多占用算子的计算时间而影响吞吐性能。

    5.jpg

    Unaligned Checkpoint 在反压严重的场景下可以明显加速 Checkpoint 的完成时间,因为它不再依赖于整体的计算吞吐能力,而和系统的存储性能更加相关,相当于计算和存储的解耦。但是它的使用也有一定的局限性,它会增加整体 State 的大小,对存储 IO 带来额外的开销,因此在 IO 已经是瓶颈的场景下就不太适合使用 Unaligned Checkpoint 机制。

    1.11.0 中 Unaligned Checkpoint 还没有作为默认模式,需要用户手动配置来开启,并且只在 Exactly-Once 模式下生效。但目前还不支持 Savepoint 模式,因为 Savepoint 涉及到作业的 Rescale 场景,Channel State 目前还不支持 State 拆分,在后面的版本会进一步支持,所以 Savepoint 目前还是会使用之前的 Aligned 模式,在反压场景下有可能需要很长时间才能完成。

    四 总结

    Flink 1.11.0 版本的开发过程中,我们看到越来越多来自中国的贡献者参与到核心功能的开发中,见证了 Flink 在中国的生态发展越来越繁荣,比如来自腾讯公司的贡献者参与了 K8s、Checkpoint 等功能开发,来自字节跳动公司的贡献者参与了 Table & SQL 层以及引擎网络层的一些开发。希望更多的公司能够参与到 Flink 开源社区中,分享在不同领域的经验,使 Flink 开源技术一直保持先进性,能够普惠到更多的受众。

    经过 1.11.0 “小版本”的短暂调整,Flink 正在酝酿下一个大版本的 Feature,相信一定会有很多重量级的特性登场,让我们拭目以待!

    ]]>
    必须掌握的分布式文件存储系统—HDFS-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 HDFS(Hadoop Distributed File System)分布式文件存储系统,主要为各类分布式计算框架如Spark、MapReduce等提供海量数据存储服务,同时HBase、Hive底层存储也依赖于HDFS。HDFS提供一个统一的抽象目录树,客户端可通过路径来访问文件,如hdfs://namenode:port/dir-a/a.data。HDFS集群分为两大角色:Namenode、Datanode(非HA模式会存在Secondary Namenode)

    Namenode

    Namenode是HDFS集群主节点,负责管理整个文件系统的元数据,所有的读写请求都要经过Namenode。

    元数据管理

    Namenode对元数据的管理采用了三种形式:

    1) 内存元数据:基于内存存储元数据,元数据比较完整

    2) fsimage文件:磁盘元数据镜像文件,在NameNode工作目录中,它不包含block所在的Datanode 信息

    3) edits文件:数据操作日志文件,用于衔接内存元数据和fsimage之间的操作日志,可通过日志运算出元数据
    fsimage + edits = 内存元数据

    注意:当客户端对hdfs中的文件进行新增或修改时,操作记录首先被记入edit日志文件,当客户端操作成功后,相应的元数据会更新到内存元数据中

    可以通过hdfs的一个工具来查看edits中的信息
    bin/hdfs  oev  -i  edits  -o  edits.xml
    查看fsimage
    bin/hdfs  oiv  -i  fsimage_0000000000000000087  -p  XML  -o  fsimage.xml

    元数据的checkpoint(非HA模式)

    Secondary Namenode每隔一段时间会检查Namenode上的fsimage和edits文件是否需要合并,如触发设置的条件就开始下载最新的fsimage和所有的edits文件到本地,并加载到内存中进行合并,然后将合并之后获得的新的fsimage上传到Namenode。checkpoint操作的触发条件主要配置参数:

    dfs.namenode.checkpoint.check.period=60  #检查触发条件是否满足的频率,单位秒
    dfs.namenode.checkpoint.dir=file://${hadoop.tmp.dir}/dfs/namesecondary
    dfs.namenode.checkpoint.edits.dir=${dfs.namenode.checkpoint.dir}
    #以上两个参数做checkpoint操作时,secondary namenode的本地工作目录,主要处理fsimage和edits文件的
    dfs.namenode.checkpoint.max-retries=3  #最大重试次数
    dfs.namenode.checkpoint.period=3600  #两次checkpoint之间的时间间隔3600秒
    dfs.namenode.checkpoint.txns=1000000  #两次checkpoint之间最大的操作记录

    checkpoint作用

    1.加快Namenode启动

    Namenode启动时,会合并磁盘上的fsimage文件和edits文件,得到完整的元数据信息,但如果fsimage和edits文件非常大,这个合并过程就会非常慢,导致HDFS长时间处于安全模式中而无法正常提供服务。SecondaryNamenode的checkpoint机制可以缓解这一问题

    2.数据恢复
    Namenode和SecondaryNamenode的工作目录存储结构完全相同,当Namenode故障退出需要重新恢复时,可以从SecondaryNamenode的工作目录中将fsimage拷贝到Namenode的工作目录,以恢复Namenode的元数据。但是SecondaryNamenode最后一次合并之后的更新操作的元数据将会丢失,最好Namenode元数据的文件夹放在多个磁盘上面进行冗余,降低数据丢失的可能性。

    注意事项:

    1.SecondaryNamenode只有在第一次进行元数据合并时需要从Namenode下载fsimage到本地。SecondaryNamenode在第一次元数据合并完成并上传到Namenode后,所持有的fsimage已是最新的fsimage,无需再从Namenode处获取,而只需要获取edits文件即可。

    2.SecondaryNamenode从Namenode上将要合并的edits和fsimage拷贝到自己当前服务器上,然后将fsimage和edits反序列化到SecondaryNamenode的内存中,进行计算合并。因此一般需要把Namenode和SecondaryNamenode分别部署到不同的机器上面,且SecondaryNamenode服务器配置要求一般不低于Namenode。

    3.SecondaryNamenode不是充当Namenode的“备服务器”,它的主要作用是进行元数据的checkpoint

    Datanode

    Datanode作为HDFS集群从节点,负责存储管理用户的文件块数据,并定期向Namenode汇报自身所持有的block信息(这点很重要,因为,当集群中发生某些block副本失效时,集群如何恢复block初始副本数量的问题)。

    关于Datanode两个重要的参数:

    1.通过心跳信息上报参数

    <property>
    <name>dfs.blockreport.intervalMsec</name>
    <value>3600000</value>
    <description>Determines block reporting interval in milliseconds.</description>
    </property>

    2.Datanode掉线判断时限参数

    Datanode进程死亡或者网络故障造成Datanode无法与Namenode通信时,Namenode不会立即把该Datanode判定为死亡,要经过一段时间,这段时间称作超时时长。HDFS默认的超时时长为10分钟30秒。如果定义超时时间为timeout,则超时时长的计算公式为:

    timeout = 2 * heartbeat.recheck.interval(默认5分钟) + 10 * dfs.heartbeat.interval(默认3秒)。
    <property>
            <name>heartbeat.recheck.interval</name>
            # 单位毫秒
            <value>2000</value>
    </property>
    <property>
            <name>dfs.heartbeat.interval</name>
            # 单位秒
            <value>1</value>
    </property>

    HDFS读写数据流程

    了解了Namenode和Datanode的作用后,就很容易理解HDFS读写数据流程,这个也是面试中经常问的问题。

    HDFS写数据流程

    1.jpg

    注意:
    1.文件block块切分和上传是在客户端进行的操作

    2.Datanode之间本身是建立了一个RPC通信建立pipeline

    3.客户端先从磁盘读取数据放到一个本地内存缓存,开始往Datanode1上传第一个block,以packet为单位,Datanode1收到一个packet就会传给Datanode2,Datanode2传给Datanode3;Datanode1每传一个packet会放入一个应答队列等待应答

    4.当一个block传输完成之后,客户端会通知Namenode存储块完毕,Namenode将元数据同步到内存中

    5.Datanode之间pipeline传输文件时,一般按照就近可用原则
    a) 首先就近挑选一台机器
    b) 优先选择另一个机架上的Datanode
    c) 在本机架上再随机挑选一台

    HDFS读数据流程

    2.jpg

    注意:
    1.Datanode发送数据,是从磁盘里面读取数据放入流,以packet为单位来做校验

    2.客户端以packet为单位接收,先在本地缓存,然后写入目标文件

    客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件

    HDFS HA机制

    HA:高可用,通过双Namenode消除单点故障。

    3.jpg

    双Namenode协调工作的要点:

    1.元数据管理方式需要改变
    a) 内存中各自保存一份元数据

    b) edits日志只能有一份,只有active状态的Namenode节点可以做写操作

    c) 两个Namenode都可以读取edits

    d) 共享的edits放在一个共享存储中管理(qjournal和NFS两个主流实现,图中以放在一个共享存储中管理(qjournal和为例)

    2.需要一个状态管理功能模块
    a) 实现了一个zk failover,常驻在每一个Namenode所在的节点

    b) 每一个zk failover负责监控自己所在Namenode节点,利用zk进行状态标识,当需要进行状态切换时,由zk failover来负责切换,切换时需要防止brain split现象的发生

    ]]>
    战疫期,钉钉如何扛起暴增百倍的流量?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png

    疫情期间,在线教育、在线办公需求持续井喷,钉钉作为很多企业首选的在线办公软件,用户量激增,特别是钉钉视频会议、直播的需求随之飙升。同时,钉钉为了响应教育部门“停课不停学”的号召,宣布老师们可以免费试用钉钉在线课堂。

    流量如洪流般涌入钉钉,一场资源扩容的技术挑战拉开了帷幕。中小学生集体对钉钉展开了五星分期与在线写歌“泄愤”的策略,钉钉本钉不得不在线求饶。而在大战间隙,一声感叹传出:

    流量这么大,钉钉为什么不崩?

    image.png

    从1月28日开始,钉钉音视频会议、直播的访问流量倍数级增长。作为一个在云上成长起来的产品,钉钉开启了在阿里云的资源扩容之路,满足了用户在家办公及在家上课的需求,保证了用户良好的体验,钉钉如何做到的?

    如此大型的扩容,面临着两大困境:效率与资源供应。

    人工扩容困境:效率低下

    时间太短。面对流量暴增,留给钉钉技术团队时间只有几天。从1月29日起,钉钉团队就已在阿里云上24小时开始全力扩容,截止2月2日,从最初的2W vCPU扩容到3W vCPU,仅做到了数倍扩容,还远未达到业务需求。

    购买与配置非常复杂。钉钉的系统架构包含多种资源,不同于单一的云服务器ECS服务集群,还包含SLB、MongoDB、Redis、EIP等产品。这些资源都需要一个个购买,其之间的关系也需要技人工自行配置。

    人工部署效率低、失误率高。钉钉用户群量级大。如果人工部署集群,一个人部署1个集群需要1小时左右,同时也只能操作3-4个集群,还需要大量的配置操作,很容易失误。

    部署复杂度高。集群的服务能力自闭环,支持无限扩展,但也会相应提升部署复杂度,而这次扩容涉及8个地域、16个可用区,传统部署方式扩容场景效率低下
    大规模集群管理难度大。需要快速扩容近千集群,才能满足几亿人在家办公及学生在家上课的需求。当资源上千后,就很难管理资源之间的关系了,更何况超百万的资源规模。

    人工部署,容错率比较差,排查困难。集群之间经常出现偏差,某个集群的SLB监听端口是300,另一个集群是3000,出现问题很难排查。

    除却以上困难,建立和运维如此巨大的集群规模还会带来更多的技术挑战。

    利用资源编排服务ROS,实现快速自动部署

    早在2月2日流量洪峰带来之前,钉钉就通过阿里云的资源编排服务(Resource Orchestration Service,简称 ROS)提高集群部署效率、帮助其快速扩容。而这款服务不负重托,帮助钉钉在短短2小时内新增部署了超过1万台云服务器,这个数字也创下了阿里云上快速扩容的新纪录。

    什么选择资源编排服务?

    资源编排服务是一款帮助阿里云用户简化云资源创建、更新和删除的自动化服务。其通过资源栈 (Stack) 这种逻辑集合来统一管理一组云资源(一个资源栈即为一组阿里云资源)。利用资源编排服务,云资源的创建、删除、克隆等操作都可以以资源栈为单位来完成。在 DevOps 实践中,资源编排可以轻松地克隆开发、测试、线上环境;同时,也可以更容易实现应用的整体迁移和扩容。

    基础设施即代码(Infrastructure as Code)

    资源编排服务是阿里云提供的基础设施即代码(Infrastructureas Code,简称IaC)的云产品,使用ROS可以帮助最快速地实践DevOps中关于IaC的理念。

    全自动托管服务

    ROS产品为全托管服务,无需购买维护IaC模板本身执行所使用的资源,只需要关注业务所需要使用的资源,即模板中定义的资源。尤其需要创建多个项目(对应多个资源栈)时,全托管的自动化可以更快地完成任务。

    可重复部署

    无论客户是需要部署的环境是开发,测试和生产环境,都可以使用同一套模板进行创建。指定不同的参数可以满足环境的差异化,例如,测试环境的ECS实例数是2台,而生产环境的ECS实例数是20台。或是客户需要进行多地域的部署,使用同一套模板可以进行重复的部署,从而提高部署多地域的效率。

    标准化部署

    在实践中,不同环境的细微差异往往带来非常复杂的管理成本,延长了问题诊断的时间,从而影响了业务的正常运转。通过使用ROS重复部署,可以将部署环境标准化,减少不同环境的差异,将环境的配置沉淀到模板中。再通过类似代码的严格管理流程,从而保证部署的标准性。

    统一的身份认证、安全和审计

    和其它的同类产品对比,阿里云官方出品的ROS与其它阿里云产品有着最佳的集成。集成资源访问管理(RAM)提供了统一的身份认证,而无需为单独建立用户认证体系。所有的云产品操作都通过OpenAPI调用,意味着您可以使用操作审计服务(ActionTrail)来审查所有的运维操作,包括ROS本身。

    image.png

    ROS如何服务钉钉扩容?

    定义资源模板

    ROS帮助钉钉快速创建了描述其所需要用到的阿里云资源(如 ECS 实例、数据库实例等)的模板,以定义它的集群架构。ROS提供可视化编辑器能力,可自动可使用的模板。模板完成后,ROS将自动地创建并配置这些资源,即可实现基础设施即代码(Infrastructureas Code)的理念。

    image.png

    模板解析与执行

    当ROS接收到用户创建资源栈的请求时,在执行创建前,首先会对模板进行解析。解析包括语法检查、参数校验、依赖分析等。

    依赖分析就是分析出资源间的依赖关系,目的有两个:

    保证资源创建的正确性:被依赖资源创建完成后才会创建依赖资源。
    提供并行化创建的能力:无依赖关系的资源可以并行化创建。

    模板解析完成后,ROS会按照依赖关系创建资源,只有所有前置资源完成创建,后面的资源才会开始创建,类似状态机的机制。

    该资源模板可以快速地重复部署,尤其多地域、多可用区部署的情况;同时也可以减少环境之间的偏差,将部署过程和结果标准化,减少因为环境偏差引入的系统问题。
    image.png

    总结

    钉钉使用资源编排服务ROS,扩容效率就提升了100倍,陆续为钉钉完成了10万台云服务器的快速扩容和部署,创下了阿里云上快速扩容的新纪录。

    目前ROS已经拥有平均每分钟1个集群的扩容效率、每天超百万vCPU弹性能力。未来,可以预见到,疫情结束后,数百万资源回收释放也将是一个浩大的工程。资源编排服务ROS具有一键销毁功能,自动回收集群内所有资源,避免繁琐操作及遗漏。

    弹性是云计算最大的优势,也是云计算对整个社会提供的普惠和便利,而阿里云弹性计算资源编排服务ROS作为阿里云上原生的自动化编排部署服务,让云计算的弹性发挥到极致,为钉钉提供了强有力的支持,让钉钉成为使用最频繁最流畅的平台。

    ]]>
    MySQL事务隔离级别-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 MySQL事务隔离级别

    对于数据库的隔离级别之前一直没有做详细整理,最近项目运行中发现了一个问题,所以抽时间对这块认真研究了下

    业务场景:
    服务A在处理流程中,会调用外部服务B,然后写入一条数据,服务B执行完成后,会回调服务C的接口更新服务A写入的数据。
    问题:
    在服务B回调服务C的时候总是找不到服务A写入的数据,在服务C中添加延时重试,问题依然存在,但此时查看数据库,对应的数据是已经存在。

    先说原因吧,是因为MySQL的事务默认隔离级别是:可重复读。
    在服务A调用服务B后,还没有写入数据到数据库,服务B就已经回调服务C了,服务C此时肯定是找不到对应的数据的,由于MySQL默认隔离级别是可重复读(即在一个事务中,对于同一份数据读取都是一样的),所以即使服务A已经写入了数据,服务C依然读取不到。

    解决方案:
    在服务C中的查询,不要放到一个事务里面,单独提取一个方法,后面的更新逻辑放到同一个事务中

    什么是事务?

    数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
    比如:某人在商店购买100商品,其中包括两个操作:
    1.该人账户减少100元
    2.商店账户增加100元
    这两个操作要么同时执行成功,要么同时执行失败。

    数据库事务的ACID性质

    • Atomic:原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
    • Consistent:一致性,事务完成后,所有数据的状态都是一致的,即该人的账户减少了100,商店的账户必须增加100
    • Isolation:隔离性,比如两个人从同一个账户取款,这两个事务对数据的修改必须相互隔离,具体隔离策略后面具体讲解。
    • Duration:持久性,事务完成后,对数据库数据的修改必须被持久化存储。

    数据库的隔离级别

    在单个事务中,不需要做隔离,所谓数据库隔离级别是针对在并发事务的情况下,解决导致的一系列问题,这些问题包括:脏读、不可重复读、幻读,具体隔离级别如下图:

    隔离级别 脏读 不可重复读 幻读
    SERIALIZABLE(串行化) 避免 避免 避免
    REPEATABLE READ(可重复读) 避免 避免 允许
    READ COMMITED(读已提交) 避免 允许 允许
    READ UNCOMMITED(读未提交) 允许 允许 允许

    SERIALIZABLE(串行化)

    当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。

    REPEATABLE READ(可重复读)

    一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。

    READ COMMITTED(读已提交数据)

    一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新。

    READ UNCOMMITTED(读未提交数据)

    一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新。

    没有事务隔离级别导致的问题

    如果没有数据库的隔离级别,数据库的数据是实时变化的,即每个事务都可以读到其它事务修改后的数据,下面结合实例介绍每个场景的问题。

    脏读

    定义:读到未提交更新的数据

    时间点 事务1 事务2
    T1 开始事务 --
    T2 -- 开始事务
    T3 查询账户余额为1000 --
    T4 取出100后金额为900 --
    T5 -- 查询账户金额为900(脏读)
    T6 撤销事务,余额恢复为1000 --
    T7 -- 存入100元后,金额变为1000
    T8 -- 提交事务

    如上事务1取出金额100后又回滚了,即啥都没做,但事务2存入了100,但最终的金额确还是1000,正确应该是1100。
    在T5时间节点出现了脏读,如果数据库配置了隔离级别为SERIALIZABLE、REPEATABLE READ、READ COMMITTED,在事务1没有提交的时候,事务2读取的都是原来的值就不会出现问题。

    不可重复读

    定义:在同一个数据中,两次读取到的数据不一致,读到了其他数据提交更新的数据

    时间点 事务1 事务2
    T1 开始事务 --
    T2 -- 开始事务
    T3 查询账户余额为1000 --
    T4 -- 查询账户余额为1000
    T5 取出100后金额为900 --
    T6 提交事务 --
    T7 -- 查询账户余额为900(与T4读取的不一致)

    事务2的两次读取到的数据不一致,第二次读取到了事务1提交的数据

    幻读

    定义:读取到另一个事务已提交插入或删除的数据。

    时间点 事务1 事务2
    T1 开始事务 --
    T2 -- 开始事务
    T3 -- 统计一年级1班所有的学生人数为40人
    T4 一年级1班新增一名学生 --
    T5 提交事务 --
    T6 -- 再次统计一年级1班的所有学生人数为41人

    事务2第一次统计人数为40人,第二次统计为41人,两次统计的结果不一致,同样如果T4时间节点转走一名学生,也会出现不一致

    不可重复读和幻读看起来比较类似,都是一个事务里面读取到两次不同的结果
    本质的区别是:不可重复读是由于数据更新导致数据不一致导致,幻读是由于插入或删除了数据导致的。

    ]]>
    上亿数据怎么玩深度分页?兼容MySQL + ES + MongoDB-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 面试题 & 真实经历

    面试题:在数据量很大的情况下,怎么实现深度分页?

    大家在面试时,或者准备面试中可能会遇到上述的问题,大多的回答基本上是分库分表建索引,这是一种很标准的正确回答,但现实总是很骨感,所以面试官一般会追问你一句,现在工期不足,人员不足,该怎么实现深度分页?

    这个时候没有实际经验的同学基本麻爪,So,请听我娓娓道来。

    惨痛的教训

    首先必须明确一点:深度分页可以做,但是深度随机跳页绝对需要禁止。

    上一张图:

    你们猜,我点一下第142360页,服务会不会爆炸?

    MySQLMongoDB数据库还好,本身就是专业的数据库,处理的不好,最多就是慢,但如果涉及到ES,性质就不一样了,我们不得不利用 SearchAfter Api,去循环获取数据,这就牵扯到内存占用的问题,如果当时代码写的不优雅,直接就可能导致内存溢出。

    为什么不能允许随机深度跳页

    从技术的角度浅显的聊一聊为什么不能允许随机深度跳页,或者说为什么不建议深度分页

    MySQL

    分页的基本原理:

    SELECT * FROM test ORDER BY id DESC LIMIT 10000, 20;

    LIMIT 10000 , 20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行。如果是LIMIT 1000000 , 100,需要扫描1000100 行,在一个高并发的应用里,每次查询需要扫描超过100W行,不炸才怪。

    MongoDB

    分页的基本原理:

    db.t_data.find().limit(5).skip(5);

    同样的,随着页码的增大,skip 跳过的条目也会随之变大,而这个操作是通过 cursor 的迭代器来实现的,对于cpu的消耗会非常明显,当页码非常大时且频繁时,必然爆炸。

    ElasticSearch

    从业务的角度来说,ElasticSearch不是典型的数据库,它是一个搜索引擎,如果在筛选条件下没有搜索出想要的数据,继续深度分页也不会找到想要的数据,退一步讲,假如我们把ES作为数据库来使用进行查询,在进行分页的时候一定会遇到max_result_window 的限制,看到没,官方都告诉你最大偏移量限制是一万。

    查询流程:

    1. 如查询第501页,每页10条,客户端发送请求到某节点
    2. 此节点将数据广播到各个分片,各分片各自查询前 5010 条数据
    3. 查询结果返回至该节点,然后对数据进行整合,取出前 5010 条数据
    4. 返回给客户端

    由此可以看出为什么要限制偏移量,另外,如果使用 Search After 这种滚动式API进行深度跳页查询,也是一样需要每次滚动几千条,可能一共需要滚动上百万,千万条数据,就为了最后的20条数据,效率可想而知。

    再次和产品对线

    俗话说的好,技术解决不了的问题,就由业务来解决!

    在实习的时候信了产品的邪,必须实现深度分页 + 跳页,如今必须拨乱反正,业务上必须有如下更改:

    • 尽可能的增加默认的筛选条件,如:时间周期,目的是为了减少数据量的展示
    • 修改跳页的展现方式,改为滚动显示,或小范围跳页

    滚动显示参考图:

    小规模跳页参考图:

    通用解决方案

    短时间内快速解决的方案主要是以下几点:

    • 必备:对排序字段,筛选条件务必设置好索引
    • 核心:利用小范围页码的已知数据,或者滚动加载的已知数据,减少偏移量
    • 额外:如果遇到不好处理的情况,也可以获取多余的数据,进行一定的截取,性能影响并不大

    MySQL

    原分页SQL:

    # 第一页
    SELECT * FROM `year_score` where `year` = 2017 ORDER BY id limit 0, 20;
    
    # 第N页
    SELECT * FROM `year_score` where `year` = 2017 ORDER BY id limit (N - 1) * 20, 20; 

    通过上下文关系,改写为:

    # XXXX 代表已知的数据
    SELECT * FROM `year_score` where `year` = 2017 and id > XXXX ORDER BY id limit 20;

    没内鬼,来点干货!SQL优化和诊断 一文中提到过,LIMIT会在满足条件下停止查询,因此该方案的扫描总量会急剧减少,效率提升Max!

    ES

    方案和MySQL相同,此时我们就可以随用所欲的使用 FROM-TO Api,而且不用考虑最大限制的问题。

    MongoDB

    方案基本类似,基本代码如下:

    相关性能测试:

    如果非要深度随机跳页

    如果你没有杠过产品经理,又该怎么办呢,没关系,还有一丝丝的机会。

    SQL优化 一文中还提到过MySQL深度分页的处理技巧,代码如下:

    # 反例(耗时129.570s)
    select * from task_result LIMIT 20000000, 10;
    
    # 正例(耗时5.114s)
    SELECT a.* FROM task_result a, (select id from task_result LIMIT 20000000, 10) b where a.id = b.id;
    
    # 说明
    # task_result表为生产环境的一个表,总数据量为3400万,id为主键,偏移量达到2000万

    该方案的核心逻辑即基于聚簇索引,在不通过回表的情况下,快速拿到指定偏移量数据的主键ID,然后利用聚簇索引进行回表查询,此时总量仅为10条,效率很高。

    因此我们在处理MySQLESMongoDB时,也可以采用一样的办法:

    1. 限制获取的字段,只通过筛选条件,深度分页获取主键ID
    2. 通过主键ID定向查询需要的数据

    瑕疵:当偏移量非常大时,耗时较长,如文中的 5s

    最后

    参考文章:MongoDB中文社区

    如果觉得对你有用的话,不要忘记点个赞啊~

    ]]>
    明了 | MongoDB 外键的基本使用-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 小小继续进行学习,这次学习的内容是MongoDB外键的基本使用。

    表示表关系的方法

    在传统的关系型数据库当中,表示表关系,数据是通过索引来完善。
    而在MongoDB中,表示表关系,使用的是嵌套,即,一个文档嵌套一个文档的方法,作为MongoDB的两个文档的关联,以及使用,reference link作为文档和文档之间的关联。

    文档嵌套

    使用可视化的

    这里使用可视化的编辑器作为文档嵌套
    输入以下的文档对象

    {
        "ming": "ming",
            "ming2": {
                "ming3": "ming8"
            }
    }

    插入成功以后如下

    这样就完成了文档的嵌套,即,表示两个文档之间的关联。

    使用JDK

    这里使用JDK进行连接。
    首先添加依赖

    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongodb-driver</artifactId>
        <version>3.5.0</version>
    </dependency>

    进行连接

      try{   
           // 连接到 mongodb 服务
             MongoClient mongoClient = new MongoClient( "106.53.115.12" , 27017 );
           
             // 连接到数据库
             MongoDatabase mongoDatabase = mongoClient.getDatabase("koa");  
           System.out.println("Connect to database successfully");
            
          }catch(Exception e){
            System.err.println( e.getClass().getName() + ": " + e.getMessage() );
         }

    进行插入

    List<Document> collections = new ArrayList<Document>();
    Document d1 = new Document();
    d1.append("name", "三国演义").append("author", "罗贯中");
    Document d2 = new Document();
    d2.append("name", "红楼梦").append("author", d1);
    collections.add(d2);
    c.insertMany(collections);

    查询出来的数据如下

    {
        "name" : "红楼梦",
        "author": {
            "name": "三国演义",
            "author": "罗贯中"
        }
    }

    此时就完成了文档的嵌套操作

    外键查询

    使用js语言,进行查询关联

    这里使用new DBRef的方式做外键查询。
    此时对于DBRef具有以下字段。

    $ref
    
             该$ref字段包含引用文档所在的集合的名称。
    
    $id
    
            该$id字段包含_id引用文档中字段的值。
    
    $db
    
          可选的。包含引用文档所在的数据库的名称。只有一些驱动程序支持$db引用,该字段说明可以跨集合关联

    这里对集合操作关联如下

    // 保存集合的数据
    > var a={value:"1"}  
     
    > var b={value:"2"}  
     
    > var c={value:"9"}  
     
    > var d={value:"10"}  
     
    > db.A.save(a)  
     
    > db.A.save(b)        
     
    > db.A.save(c)   
     
    > db.A.save(d)  
     // 进行集合数据的查询
    > db.A.find()                                                                                                 
     
    { "_id" : ObjectId("4e3f33ab6266b5845052c02b"), "value" : "1" }  
     
    { "_id" : ObjectId("4e3f33de6266b5845052c02c"), "value" : "2" }  
     
    { "_id" : ObjectId("4e3f33e06266b5845052c02d"), "value" : "9" }  
     
    { "_id" : ObjectId("4e3f33e26266b5845052c02e"), "value" : "10" }  

    进行集合关联,这里使用 new DBRef 方式完成集合的关联

    // 通过添加new DBRef 关键字,完成对集合的关联,这里通过new DBRef作为关键字,其中A为key,ObjectId 为value,进行关联
    > var Ba={Apid:[new DBRef('A',ObjectId("4e3f33de6266b5845052c02c"))],value:3}                        
     
     // 保存集合
    > db.B.save(Ba)  
     
    > var Ba={Apid:[new DBRef('A',ObjectId("4e3f33de6266b5845052c02c"))],value:4}  
     
    > db.B.insert(Ba)                                                              
     
    > var Ba={Apid:[new DBRef('A',ObjectId("4e3f33de6266b5845052c02c"))],value:7}  
     
    > db.B.insert(Ba)                                                              
     
    > var Ba={Apid:[new DBRef('A',ObjectId("4e3f33de6266b5845052c02c"))],value:8}  
     
    > db.B.insert(Ba)                                                              
     // 进行查询
    > db.B.find()  
     
     // 可以看到,已经成功关联
    { "_id" : ObjectId("4e3f3dd96266b5845052c035"), "Apid" : [ { "$ref" : "A", "$id" : ObjectId("4e3f33de6266b5845052c02c") } ], "value" : 3 }  
     
    { "_id" : ObjectId("4e3f3de16266b5845052c036"), "Apid" : [ { "$ref" : "A", "$id" : ObjectId("4e3f33de6266b5845052c02c") } ], "value" : 4 }  
     
    { "_id" : ObjectId("4e3f3dec6266b5845052c037"), "Apid" : [ { "$ref" : "A", "$id" : ObjectId("4e3f33de6266b5845052c02c") } ], "value" : 7 }  
     
    { "_id" : ObjectId("4e3f3df06266b5845052c038"), "Apid" : [ { "$ref" : "A", "$id" : ObjectId("4e3f33de6266b5845052c02c") } ], "value" : 8 } 

    使用mongo-java的方式操作

                // 创建外键进行关联,其中外键为refB
                // 其中选择对db数据库进行操作,选择id作为数据库的关联
                DBRef refB = new DBRef(db,"transations", obj.get("_id"));
                // 创建新的集合
                DBObject subObj = new BasicDBObject();
                // 外键插入
                subObj.put("brand", refB);
                // 进行保存
                accounts.save(subObj);

    查询的结果如下

    可以看到其中$id 对应的值为其数据库mongodb的外键。
    这样就完成了对数据库的外键操作。

    公众号

    ]]>
    MySQL 8.0.21 JSON_VALUE() 介绍-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 MySQL 8.0.21发布了,其中一个新特性是JSON_VALUE()函数。主要的动机是简化JSON数据的索引创建,但是还有更多的原因。

    JSON_VALUE()在JSON数据中查找指定的标量JSON值,并将其作为SQL值返回。

    例子,我将使用mysql_x示例数据库数据作为示例。让我们从life expectancy数据开始。

    SELECT JSON_EXTRACT(doc, "$.demographics.LifeExpectancy") AS raw 
    FROM countryinfo 
    LIMIT 4;
    +--------------------+
    | raw                |
    +--------------------+
    | 78.4000015258789   |
    | 45.900001525878906 |
    | 38.29999923706055  |
    | 76.0999984741211   |
    +--------------------+

    这是一个很棒的信息,但是用眼睛看起来不是那么舒服

    我们可以使用JSON_VALUE()使它对我们看起来说更容易一些

    SELECT 
    JSON_VALUE(doc, "$.demographics.LifeExpectancy" RETURNING DECIMAL(6,2)) AS trimmed FROM countryinfo 
    LIMIT 4;
    +---------+
    | trimmed |
    +---------+
    |   78.40 |
    |   45.90 |
    |   38.30 |
    |   76.10 |
    +---------+

    它在WHERE子句中非常有用。在本例中没有RETURNING子句。

    SELECT doc->"$.Name" 
    FROM countryinfo 
    WHERE JSON_VALUE(doc, "$.demographics.LifeExpectancy" 
         RETURNING DECIMAL(6,3)) > 80.1;
    +---------------+
    | doc->"$.Name" |
    +---------------+
    | "Andorra"     |
    | "Japan"       |
    | "Macao"       |
    | "San Marino"  |
    +---------------+

    可选的RETURN子句将您的数据转换为FLOAT、DOUBLE、DECIMAL、SIGNED、
    UNSIGNED, DATE, TIME, DATETIME, CHAR或JSON, 甚至还有on empty和on error子句。

    SELECT JSON_VALUE('{firstname:"John"}', '$.lastname'               
            DEFAULT 'No last name found' ON ERROR) AS "Last Name";
    +--------------------+
    | Last Name          |
    +--------------------+
    | No last name found |
    +--------------------+          

    当然,它可以用来帮助定义索引。

    CREATE TABLE xdemo (j JSON,
     x INTEGER,  
     key((json_value(j, '$.id'))) 
    );

    翻译来源:https://elephantdolphin.blogspot.com/2020/07/jsonvalue-now-in-mysql-8021.html

    ]]>
    同样是查询语言,它和 SQL 竟然有这么多不同-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 摘要:这篇文章将介绍图数据库 Nebula Graph 的查询语言 nGQL 和 SQL 的区别。

    本文首发于 Nebula Graph 官方博客:https://nebula-graph.com.cn/posts/sql-vs-ngql-comparison/

    sql-vs-ngql

    虽然本文主要介绍 nGQL 和 SQL 的区别,但是我们不会深入探讨这两种语言,而是将这两种语言做对比,以帮助你从 SQL 过渡到 nGQL。

    SQL (Structured Query Language) 是具有数据操纵和数据定义等多种功能的数据库语言,这种语言是一种特定目的编程语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理。

    nGQL 是一种类 SQL 的声明型的文本查询语言,相比于 SQL, nGQL 为可扩展、支持图遍历、模式匹配、分布式事务(开发中)的图数据库查询语言。

    概念对比

    对比项 SQL nGQL
    点类型 tag
    边类型 edge type
    点 ID 主键 vid
    边 ID 复合主键 起点、终点、rank
    点或边的属性
    点或边

    语法对比

    数据定义语言 (DDL)

    数据定义语言(DDL)用于创建或修改数据库的结构,也就是 schema。

    对比项 SQL nGQL
    创建图空间(数据库) CREATE DATABASE <database_name> CREATE SPACE <space_name>
    列出图空间(数据库) SHOW DATABASES SHOW SPACES
    使用图空间(数据库) USE <database_name> USE <space_name>
    删除图空间(数据库) DROP DATABASE <database_name> DROP SPACE <space_name>
    修改图空间(数据库) ALTER DATABASE <database_name> alter_option
    创建 tags/edges CREATE TAG | EDGE <tag_name>
    创建表 CREATE TABLE <tbl_name> (create_definition,...)
    列出表列名 SHOW COLUMNS FROM <tbl_name>
    列出 tags/edges SHOW TAGS | EDGES
    Describe tags/edge DESCRIBE TAG | EDGE ` edge_name>`
    修改 tags/edge ALTER TAG | EDGE ` edge_name>`
    修改表 ALTER TABLE <tbl_name>

    索引

    对比项 SQL nGQL
    创建索引 CREATE INDEX CREATE {TAG | EDGE} INDEX
    删除索引 DROP INDEX DROP {TAG | EDGE} INDEX
    列出索引 SHOW INDEX FROM SHOW {TAG | EDGE} INDEXES
    重构索引 ANALYZE TABLE REBUILD {TAG | EDGE} INDEX <index_name> [OFFLINE]

    数据操作语言(DML)

    数据操作语言(DML)用于操作数据库中的数据。

    对比项 SQL nGQL
    插入数据 INSERT IGNORE INTO <tbl_name> [(col_name [, col_name] ...)] {VALUES | VALUE} [(value_list) [, (value_list)] INSERT VERTEX <tag_name> (prop_name_list[, prop_name_list]) {VALUES | VALUE} vid: (prop_value_list[, prop_value_list]) INSERT EDGE <edge_name> ( <prop_name_list> ) VALUES | VALUE <src_vid> -> <dst_vid>[@<rank>] : ( <prop_value_list> )
    查询数据 SELECT GO, FETCH
    更新数据 UPDATE <tbl_name> SET field1=new-value1, field2=new-value2 [WHERE Clause] UPDATE VERTEX <vid> SET <update_columns> [WHEN <condition>] UPDATE EDGE <edge> SET <update_columns> [WHEN <condition>]
    删除数据 DELETE FROM <tbl_name> [WHERE Clause] DELETE EDGE <edge_type> <vid> -> <vid>[@<rank>] [, <vid> -> <vid> ...] DELETE VERTEX <vid_list>
    拼接数据 JOIN ` `

    数据查询语言(DQL)

    数据查询语言(DQL)语句用于执行数据查询。本节说明如何使用 SQL 语句和 nGQL 语句查询数据。

    SELECT
     [DISTINCT]
     select_expr [, select_expr] ...
     [FROM table_references]
     [WHERE where_condition]
     [GROUP BY {col_name | expr | position}]
     [HAVING  where_condition]
     [ORDER BY {col_name | expr | position} [ASC | DESC]]
    GO [[<M> TO] <N> STEPS ] FROM <node_list>
     OVER <edge_type_list> [REVERSELY] [BIDIRECT]
     [WHERE where_condition]
     [YIELD [DISTINCT] <return_list>]
     [| ORDER BY <expression> [ASC | DESC]]
     [| LIMIT [<offset_value>,] <number_rows>]
     [| GROUP BY {col_name | expr | position} YIELD <col_name>]
    
    <node_list>
       | <vid> [, <vid> ...]
       | $-.id
    
    <edge_type_list>
       edge_type [, edge_type ...]
    
    <return_list>
        <col_name> [AS <col_alias>] [, <col_name> [AS <col_alias>] ...]

    数据控制语言(DCL)

    数据控制语言(DCL)包含诸如 GRANTREVOKE 之类的命令,这些命令主要用来处理数据库系统的权限、其他控件。

    对比项 SQL nGQL
    创建用户 CREATE USER CREATE USER
    删除用户 DROP USER DROP USER
    更改密码 SET PASSWORD CHANGE PASSWORD
    授予权限 GRANT <priv_type> ON [object_type] TO <user> GRANT ROLE <role_type> ON <space> TO <user>
    删除权限 REVOKE <priv_type> ON [object_type] TO <user> REVOKE ROLE <role_type> ON <space> FROM <user>

    数据模型

    查询语句基于以下数据模型:

    RDBMS 关系结构图

    RDBMS

    Nebula Graph 最小模型图

    Studio

    本文将使用 NBA 数据集。该数据集包含两种类型的点,也就是两个标签,即 playerteam ;两种类型的边,分别是 servefollow

    在关系型数据管理系统中(RDBMS)中,我们用表来表示点以及与点相关的边(连接表)。因此,我们创建了以下表格:playerteamservefollow。在 Nebula Graph 中,基本数据单位是顶点和边。两者都可以拥有属性,相当于 RDBMS 中的属性。

    Nebula Graph 中,点之间的关系由边表示。每条边都有一种类型,在 NBA 数据集中,我们使用边类型 servefollow 来区分两种类型的边。

    示例数据

    在 RDBMS 插入数据

    首先,让我们看看如何在 RDBMS 中插入数据。我们先创建一些表,然后为这些表插入数据。

    CREATE TABLE player (id INT, name VARCHAR(100), age INT);
    CREATE TABLE team (id INT, name VARCHAR(100));
    CREATE TABLE serve (player_id INT, team_id INT, start_year INT, end_year INT);
    CREATE TABLE follow (player_id1 INT, player_id2 INT, degree INT);

    然后插入数据。

    INSERT INTO player
    VALUES
       (100, 'Tim Duncan', 42),
       (101, 'Tony Parker', 36),
       (102, 'LaMarcus Aldridge', 33),
       (103, 'Rudy Gay',32),
       (104, 'Marco Belinelli', 32),
       (105, 'Danny Green', 31),
       (106, 'Kyle Anderson', 25),
       (107, 'Aron Baynes', 32),
       (108, 'Boris Diaw', 36),
       (109, 'Tiago Splitter', 34),
       (110, 'Cory Joseph', 27);
    
    INSERT INTO team
    VALUES
       (200, 'Warriors'),
       (201, 'Nuggets'),
       (202, 'Rockets'),
       (203, 'Trail'),
       (204, 'Spurs'),
       (205, 'Thunders'),
       (206, 'Jazz'),
       (207, 'Clippers'),
       (208, 'Kings');
    
    INSERT INTO serve
    VALUES
       (100,200,1997,2016),
       (101,200,1999,2010),
       (102,200,2001,2005),
       (106,200,2000,2011),
       (107,200,2001,2009),
       (103,201,1999,2018),
       (104,201,2006,2015),
       (107,201,2007,2010),
       (108,201,2010,2016),
       (109,201,2011,2015),
       (105,202,2015,2019),
       (109,202,2017,2019),
       (110,202,2007,2009);
    
    INSERT INTO follow
    VALUES
       (100,101,95),
       (100,102,91),
       (100,106,90),
       (101,100,95),
       (101,102,91),
       (102,101,75),
       (103,102,70),
       (104,103,50),
       (104,105,60),
       (105,104,83),
       (105,110,87),
       (106,100,88),
       (106,107,81),
       (107,106,92),
       (107,108,97),
       (108,109,95),
       (109,110,78),
       (110,109,72),
       (110,105,85);

    在 Nebula Graph 插入数据

    Nebula Graph 中插入数据与上述类似。首先,我们需要定义好数据结构,也就是创建好 schema。然后可以选择手动或使用 Nebula Graph Studio (Nebula Graph 的可视化工具)导入数据。这里我们手动添加数据。

    在下方的 INSERT 插入语句中,我们向图空间 NBA 插入了球员数据(这和在 MySQL 中插入数据类似)。

    INSERT VERTEX player(name, age) VALUES
    100: ('Tim Duncan', 42),
    101: ('Tony Parker', 36),
    102: ('LaMarcus Aldridge', 33),
    103: ('Rudy Gay', 32),
    104: ('Marco Belinelli', 32),
    105: ('Danny Green', 31),
    106: ('Kyle Anderson', 25),
    107: ('Aron Baynes', 32),
    108: ('Boris Diaw', 36),
    109: ('Tiago Splitter', 34),
    110: ('Cory Joseph', 27);

    考虑到篇幅限制,此处我们将跳过插入球队和边的重复步骤。你可以点击此处下载示例数据亲自尝试。

    Nebula-Graph-Studio

    增删改查(CRUD)

    本节介绍如何使用 SQL 和 nGQL 语句创建(C)、读取(R)、更新(U)和删除(D)数据。

    插入数据

    mysql> INSERT INTO player VALUES (100, 'Tim Duncan', 42);
    
    nebula> INSERT VERTEX player(name, age) VALUES 100: ('Tim Duncan', 42);

    查询数据

    查找 ID 为 100 的球员并返回其 name 属性:

    mysql> SELECT player.name FROM player WHERE player.id = 100;
    
    nebula> FETCH PROP ON player 100 YIELD player.name;

    更新数据

    mysql> UPDATE player SET name = 'Tim';
    
    nebula> UPDATE VERTEX 100 SET player.name = "Tim";

    删除数据

    mysql> DELETE FROM player WHERE name = 'Tim';
    
    nebula> DELETE VERTEX 121;
    nebula> DELETE EDGE follow 100 -> 200;

    建立索引

    返回年龄超过 36 岁的球员。

    SELECT player.name
    FROM player
    WHERE player.age < 36;

    使用 nGQL 查询有些不同,因为您必须在过滤属性之前创建索引。更多信息请参见 索引文档

    CREATE TAG INDEX player_age ON player(age);
    REBUILD TAG INDEX player_age OFFLINE;
    LOOKUP ON player WHERE player.age < 36;

    示例查询

    本节提供一些示例查询供您参考。

    示例 1

    在表 player 中查询 ID 为 100 的球员并返回其 name 属性。

    SELECT player.name
    FROM player
    WHERE player.id = 100;

    接下来使用 Nebula Graph 查找 ID 为 100 的球员并返回其 name 属性。

    FETCH PROP ON player 100 YIELD player.name;

    Nebula Graph 使用 FETCH 关键字获取特定点或边的属性。本例中,属性即为点 100 的名称。nGQL 中的 YIELD 关键字相当于 SQL 中的 SELECT

    示例 2

    查找球员 Tim Duncan 并返回他效力的所有球队。

    SELECT a.id, a.name, c.name
    FROM player a
    JOIN serve b ON a.id=b.player_id
    JOIN team c ON c.id=b.team_id
    WHERE a.name = 'Tim Duncan'

    使用如下 nGQL 语句完成相同操作:

    CREATE TAG INDEX player_name ON player(name);
    REBUILD TAG INDEX player_name OFFLINE;
    LOOKUP ON player WHERE player.name == 'Tim Duncan' YIELD player.name AS name | GO FROM $-.VertexID OVER serve YIELD $-.name, $$.team.name;

    这里需要注意一下,在 nGQL 中的等于操作采用的是 C 语言风格的 ==,而不是SQL风格的 =

    示例 3

    以下查询略复杂,现在我们来查询球员 Tim Duncan 的队友。

    SELECT a.id, a.name, c.name
    FROM player a
    JOIN serve b ON a.id=b.player_id
    JOIN team c ON c.id=b.team_id
    WHERE c.name IN (SELECT c.name
    FROM player a
    JOIN serve b ON a.id=b.player_id
    JOIN team c ON c.id=b.team_id
    WHERE a.name = 'Tim Duncan')

    nGQL 则使用管道将前一个子句的结果作为下一个子句的输入。

    GO FROM 100 OVER serve YIELD serve._dst AS Team | GO FROM $-.Team OVER serve REVERSELY YIELD $$.player.name;

    您可能已经注意到了,我们仅在 SQL 中使用了 JOIN。这是因为 Nebula Graph 只是使用类似 Shell 的管道对子查询进行嵌套,这样更符合我们的阅读习惯也更简洁。

    参考资料

    我们建议您亲自尝试上述查询语句,这将帮您更好地理解 SQL 和 nGQL,并节省您上手 nGQL 的学习时间。以下是一些参考资料:

    作者有话说:Hi,Hi ,大家好,我是 Amber,Nebula Graph 的文档工程师,希望上述内容可以给大家带来些许启发。限于水平,如有不当之处还请斧正,在此感谢^^

    喜欢这篇文章?来来来,给我们的 GitHub 点个 star 表鼓励啦~~ ]]> DataWorks百问百答44:如何同步JSON里的部分数据?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在配置MongoDB同步任务时,有些用户希望截取部分数据,下面以截取json里的name:hz为例,下面图片里是MongoDB的数据,是以json的格式展现的。
    dataworks44-1.png

    • 在配置同步任务column时要采用下面的方法:
    "column":[
      {
        "name":"operation.name",
        "type":"document.string"
      }
    ]
    • 这样会在对应的目标列展现数据 hz

    DataWorks百问百答历史记录 请点击这里查看>>

    更多DataWorks技术和产品信息,欢迎加入【DataWorks钉钉交流群】

    ]]>
    PostgreSQL公开课(第19期)-Toast技术特点与应用-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1、Toast简介

    2、Toast的存储方式

    3、Toast4种压缩策略

    4、Toast表的计算方式

    5、Toast表的优点与缺点

    6、与Oracle大对象存储方式对比

    由于PG数据库不支持一行的数据跨越多块存储,Toast技术解决了大字段值的存储问题,类似于Oracle的大对象储存方式,本次技术沙龙全方面介绍Toast技术特点与应用场景。

    道可道,非常道;名可名,非常名。

    时间:2020-07-26 20:00-21:00

    主讲:CUUG陈卫星老师

    地址:腾讯课堂搜索‘cuug’

    ]]>
    DataHub使用指南-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 快速入门教程

    1.开通DataHub

    ​ 使用DataHub的第一步,首先点击开通DataHub

    2.创建Project和 Topic

    • 具体创建方式参考文档:https://help.aliyun.com/document_detail/158785.html?spm=a2c4g.11186623.6.556.796958e1yVcaLO
    • 创建Topic方式解读,Tuple还是Blob?

      • Tuple支持的是强Schema的结构化数据,Blob指的是没有类型的非结构化数据,在实际中Blob就是只有一列为string类型的schema
      • 值得注意的是:使用Blob类型topic的话,数据会采用Base64加密,无论是抽样还是下游消费,都需要进行解密
      • Schema设计


    DataHub目前只支持字段的新增,不支持删除和修改,针对上游数据源字段经常发生变动的场景,建议设置允许字段为空,如果上游字段变更的话,针对多出来的字段可以通过SDK新增字段,而对于减少的字段则由于允许为空,值将会置为NULL,不会对业务造成影响
    
    • shard 和生命周期设置

      • shard在DataHub中代表的是并发通道,每个shard每秒吞吐限制为5M/s,每个shardQPS(每秒请求数)为2000次,您可根据这两项指标合理设置shard个数
      • 针对生命周期而言,可以根据业务需要设置,如果需要更改的话,可以使用Java SDK修改生命周期

    3.上游的选择

    DataHub目前支持的数据采集插件

    • OGG

      • OGG for MySQL
      • OGG for Oracle
    • LogStash
    • Flume
    • Canal插件
    • Fluentd

    https://help.aliyun.com/document_detail/158836.html?spm=a2c4g.11186623.6.588.5e65710b7RMCns
    通过SDK自定义写入DataHub

    注意:

    https://help.aliyun.com/document_detail/158841.html?spm=a2c4g.11186623.6.599.28c21333xe8wPo
    https://help.aliyun.com/document_detail/158834.html?spm=a2c4g.11186623.6.583.2db4710bEEOlFZ
    兼容Kafka

    https://help.aliyun.com/document_detail/168118.html?spm=a2c4g.11186623.6.586.6aec6bdbCi1ElZ
    DTS数据同步
    从PolarDB MySQL同步至Datahub
    从DRDS同步至DataHub
    DataHub目前的上游生态就是这样了

    4.指标查看 or数据抽样

    ​ 在将数据写入到DataHub之后,DataHub提供了可视化指标来查看内部情况,具体详情请查看

    指标查看metric详情最新.png

    ​ 用户如何查看数据质量,写入是否正确?可以通过Web抽样功能来查看数据

    5.订阅

    ​ 什么是订阅?

    • 订阅最主要的功能就是存储消费点位,以及通过点位重置重新消费
    • 用户可创建不同的订阅针对同一个Topic数据的不同消费模式
    • 创建同步自动会创建对应的订阅


    创建订阅,删除订阅请查看文档:https://help.aliyun.com/document_detail/158833.html?spm=a2c4g.11174283.6.584.78d763ef5KNv0Y

    6.同步数据到下游

    ​ 消费DataHub数据有两种方式,通过DataHub支持的同步数据库同步到下游,或者通过自定义SDK消费数据进行处理

    DataHub支持的同步类型:

    • Hologres
    • Maxcompute
    • ADS
    • ElasticSearch
    • 函数计算
    • OSS
    • TableStore
    • RDS/MySQL/ADS 3.0

    自定义SDK消费

    ​ 您可以使用SDK对DataHub数据进行消费

    ​ 同时DataHub协同消费解决多个消费者同时消费一个topic时,自动分配shard的问题,您也可以选择使用协同消费对DataHub数据进行处理

    ​ 同步往往是出现问题最多的,请参考  DataHub同步问题

    7.监控报警

    ​ 在同步数据过程中,DataHub支持了监控报警,目前只有订阅延迟报警这一项,您可以通过创建报警规则方式对DataHub同步到下游数据进行监控,当超过延迟时间阈值时,会通过钉钉、短信等多种方式提醒您。

    ​ 具体报警说明请查看文档:监控报警

    8 总结

    本文通过对DataHub的创建使用,上游数据源的选择,同步到DataHub的指标查看,以及下游类型的说明,阐述了DataHub做为数据通道的概念模型以及实际的落地场景,如有更多使用疑问,请加DataHub公共云群组进行反馈

    ]]>
    慢SQL优化实战笔记-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一、存在问题

    经过sql慢查询的优化,我们系统中发现了以下几种类型的问题:

    1.未建索引:整张表没有建索引;
    
    2.索引未命中:有索引,但是部分查询条件下索引未命中;
    
    3.搜索了额外的非必要字段,导致回表;
    
    4.排序,聚合导致慢查询;
    
    5.相同内容多次查询数据库;
    
    6.未消限制搜索范围或者限制的搜索范围在预期之外,导致全部扫描;

    二、解决方案

    1.优化索引,增加或者修改当前的索引;         
    
    2.重写sql;
    
    3.利用redis缓存,减少查询次数;
    
    4.增加条件,避免非必要查询;
    
    5.增加条件,减少查询范围;                          

    三、案例分析

    (一)药材搜索接口

    完整sql语句在附录,为方便阅读和脱敏,部分常用字段采用中文。

    这儿主要讲一下我们拿到Sql语句后的整个分析过程,思考逻辑,然后进行调整的过程和最后解决的办法。

    给大家提供一些借鉴,也希望大家能够提出更好的建议。                

    这个sql语句要求是根据医生搜索的拼音或者中文,进行模糊查询,找到药材,然后根据医生选择的药库,查找下面的供应商,然后根据供应商,进行药材匹配,排除掉供应商没有的药材,然后根据真名在前,别名在后,完全匹配在前,部分匹配在后,附加医生最近半年的使用习惯,把药材排序出来。最后把不同名称的同一味药聚合起来,以真名(另名)的形式展现。

    1.分析sql

    • (1)14-8

    第14排,id为8的explain结果分析:

    ①Explain
    8,DERIVED,ssof,range,"ix_district,ix_供应商id",ix_district,8,NULL,18,Using where; Using index; Using temporary
    ②Sql
    SELECT DISTINCT (ssof.供应商id) AS 供应商id FROM  药库供应商关系表 AS ssof  WHERE ssof.药库id IN (  1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 17, 22, 24, 25, 26, 27, 31, 33)  AND ssof.药方剂型id IN (1)
    ③索引
    PRIMARY KEY (`id`),    UNIQUE KEY `ix_district` (        `药库id`, `药方剂型id`, `供应商id`    ) USING BTREE,KEY `ix_供应商id` (`供应商id`) USING BTREE
    ④分析

    使用了索引,建立了临时表,这个地方索引已经完全覆盖了,但是还有回表操作。

    原因是用in,这个导致了回表。如果in可以被mysql 自动优化为等于,就不会回表。如果无法优化,就回表。

    临时表是因为有distinct,所以无法避免。

    同时使用in需要注意,如果里面的值数量比较多,有几万个。即使区分度高,就会导致索引失效,这种情况需要多次分批查询。

    2. 12-7

    • (1)Explain
    7,DERIVED,<derived8>,ALL,NULL,NULL,NULL,NULL,18,Using temporary; Using filesort
    • (2)Sql
    INNER JOIN (上面14-8临时表) tp ON tp.供应商id= ms.供应商id
    • (3)索引

    • (4)分析

    对临时表操作,无索引,用了文件排序。

    这一部分是对临时表和药材表进行关联操作的一部分,有文件排序是因为需要对药材表id进行group by 导致的。

       1、默认情况下,mysql在使用group by之后,会产生临时表,而后进行排序(此处排序默认是快排),这会消耗的性能。

       2、group by本质是先分组后排序【而不是先排序后分组】。

       3、group by column 默认会按照column分组, 然后根据column升序排列;  group by column order by null 则默认按照column分组,然后根据标的主键ID升序排列。

    3. 13-7

    • (1)Explain
    7,DERIVED,ms,ref,"ix_title,idx_audit,idx_mutiy",idx_mutiy,5,"tp.供应商id,const",172,NULL
    • (2)Sql
    SELECT ms.药材表id, max(ms.audit) AS audit, max(ms.price) AS price, max(ms.market_price) AS market_price,max(ms.is_granule) AS is_granule,max(ms.is_decoct) AS is_decoct, max(ms.is_slice) AS is_slice,max(ms.is_cream) AS is_cream, max(ms.is_extract) AS is_extract,max(ms.is_cream_granule) AS is_cream_granule, max(ms.is_extract_granule) AS is_extract_granule,max(ms.is_drychip) AS is_drychip,            max(ms.is_pill) AS is_pill,max(ms.is_powder) AS is_powder, max(ms.is_bolus) AS is_bolus FROM 供应商药材表 AS ms INNER JOIN (                SELECT                    DISTINCT (ssof.供应商id) AS 供应商id                FROM                    药库供应商关系表 AS ssof WHERE  ssof.药库id IN (  1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 17, 22, 24, 25, 26, 27, 31, 33 ) AND ssof.药方剂型id IN (1) ) tp ON tp.供应商id= ms.供应商id WHERE  ms.audit = 1  GROUP BY  ms.药材表id
    • (3)索引
       KEY `idx_mutiy` (`供应商id`, `audit`, `药材表id`)
    • (4)分析

    命中了索引,表间连接使用了供应商id,建立索引的顺序是供应商id,where条件中audit,Group by 条件药材表id。

    这部分暂时不需要更改。

    4.10-6

    • (1)Explain
    6,DERIVED,r,range,"PRIMARY,id,idx_timeline,idx_did_timeline,idx_did_isdel_statuspay_timecreate_payorderid,idx_did_statuspay_ischecked_isdel",idx_did_timeline,8,NULL,546,Using where; Using index; Using temporary; Using filesort
    • (2)Sql
    SELECT 
           count(*) AS total, 
           rc.i AS m药材表id 
         FROM 
            处方药材表 AS rc 
            INNER JOIN 药方表AS r ON r.id = rc.药方表_id 
         WHERE 
             r.did = 40 
             AND r.timeline > 1576115196 
             AND rc.type_id in (1, 3) 
             GROUP BY 
        rc.i 
    • (3)索引
    KEY `idx_did_timeline` (`did`, `timeline`),
    • (4)分析

    驱动表与被驱动表,小表驱动大表。

    先了解在join连接时哪个表是驱动表,哪个表是被驱动表:

    1.当使用left join时,左表是驱动表,右表是被驱动表;

    2.当使用right join时,右表时驱动表,左表是驱动表;

    3.当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表;

    4. in后面跟的是驱动表, exists前面的是驱动表;

    5. 11-6

    • (1)Explain
    6,DERIVED,rc,ref,"orderid_药材表,药方表_id",药方表_id,5,r.id,3,Using where
    • (2)Sql

    同上

    • (3)索引
      KEY `idx_药方表_id` (`药方表_id`, `type_id`) USING BTREE,
    • (4)分析

    索引的顺序没有问题,仍旧是in 导致了回表。

    6.8-5

    • (1)Explain
    5,UNION,malias,ALL,id_tid,NULL,NULL,NULL,4978,Using where
    • (2)Sql
     SELECT 
          mb.id, 
          mb.sort_id, 
          mb.title, 
          mb.py, 
          mb.unit, 
          mb.weight, 
          mb.tid, 
          mb.amount_max, 
          mb.poisonous, 
          mb.is_auxiliary, 
          mb.is_auxiliary_free, 
          mb.is_difficult_powder, 
          mb.brief, 
          mb.is_fixed_recipe, 
          ASE WHEN malias.py = 'GC' THEN malias.title ELSE CASE WHEN malias.title = 'GC' THEN malias.title ELSE '' END END AS atitle, 
          alias.py AS apy, 
          CASE WHEN malias.py = 'GC' THEN 2 ELSE CASE WHEN malias.title = 'GC' THEN 2 ELSE 1 END END AS ttid 
     FROM 
          药材表 AS mb 
          LEFT JOIN 药材表 AS malias ON malias.tid = mb.id 
    WHERE 
          alias.title LIKE '%GC%' 
          OR malias.py LIKE '%GC%'
    • (3)索引 
    KEY `id_tid` (`tid`) USING BTREE,
    • (4)分析

    因为like是左右like,无法建立索引,所以只能建tid。Type是all,遍历全表以找到匹配的行,左右表大小一样,估算的找到所需的记录所需要读取的行数有4978。这个因为是like的缘故,无法优化,这个语句并没有走索引,药材表 AS mb FORCE INDEX (id_tid) 改为强制索引,读取的行数减少了700行。

    7.9-5

    • (1)Explain
    5,UNION,mb,eq_ref,"PRIMARY,ix_id",PRIMARY,4,malias.tid,1,NULL
    • (2)Sql

    同上

    • (3)索引
    PRIMARY KEY (`id`) USING BTREE,
    • (4)分析

    走了主键索引,行数也少,通过。

    8.7-4

    • (1)Explain
    4,DERIVED,mb,ALL,id_tid,NULL,NULL,NULL,4978,Using where
    • (2)Sql           
    SELECT 
           mb.id, 
           mb.sort_id, 
           mb.title, 
           mb.py, 
           mb.unit, 
           mb.weight, 
           mb.tid, 
           mb.amount_max, 
           mb.poisonous, 
           mb.is_auxiliary, 
           mb.is_auxiliary_free, 
           mb.is_difficult_powder, 
           mb.brief, 
           mb.is_fixed_recipe, 
           '' AS atitle, 
           '' AS apy, 
           CASE WHEN mb.py = 'GC' THEN 3 ELSE CASE WHEN mb.title = 'GC' THEN 3 ELSE 1 END END AS ttid 
        FROM 
           药材表 AS mb 
          WHERE 
           mb.tid = 0 
           AND (
               mb.title LIKE '%GC%' 
               OR mb.py LIKE '%GC%'
                                    )
    • (3)索引
    KEY `id_tid` (`tid`) USING BTREE,
    • (4)分析

     tid int(11) NOT NULL DEFAULT '0' COMMENT '真名药品的id',

    他也是like,这个没法优化。

    9.6-3

    • (1)Explain
    3,DERIVED,<derived4>,ALL,NULL,NULL,NULL,NULL,9154,Using filesort
    • (2)Sql

      UNION ALL

    • (3)索引

    • (4)分析

    就是把真名搜索结果和别人搜索结果合并。避免用or连接,加快速度 形成一个munion的表,初步完成药材搜索,接下去就是排序。

    这一个进行了2次查询,然后用union连接,可以考虑合并为一次查询。用case when进行区分,计算出权重。

    这边是一个优化点。

    10.4-2

    • (1)Explain
    2,DERIVED,<derived3>,ALL,NULL,NULL,NULL,NULL,9154,NULL
    • (2)Sql
    
     SELECT 
           munion.id, 
           munion.sort_id, 
           case when length(
             trim(
                  group_concat(munion.atitle SEPARATOR ' ')
                            )
                        )> 0 then concat(
                            munion.title, 
                            '(', 
                            trim(
                                group_concat(munion.atitle SEPARATOR ' ')
                            ), 
                            ')'
                        ) else munion.title end as title, 
              munion.py, 
              munion.unit, 
              munion.weight, 
              munion.tid, 
              munion.amount_max, 
              munion.poisonous, 
              munion.is_auxiliary, 
              munion.is_auxiliary_free, 
              munion.is_difficult_powder, 
              munion.brief, 
              munion.is_fixed_recipe, 
              --  trim( group_concat( munion.atitle SEPARATOR ' ' ) ) AS atitle,
                        ##  --  
               trim(
                     group_concat(munion.apy SEPARATOR ' ')
                     ) AS apy, 
                 ##   
                   max(ttid) * 100000 + id AS ttid 
             FROM 
                    munion <derived4>
                 GROUP BY 
                    id -- 全部实名药材 结束##
    • (3)索引

    • (4)分析

    这里全部在临时表中搜索了。

    11.5-2

    • (1)Explain
    2,DERIVED,<derived6>,ref,<auto_key0>,<auto_key0>,5,m.id,10,NULL
    • (2)Sql
    Select fields from 全部实名药材表 as m  LEFT JOIN ( 个人使用药材统计表 ) p ON m.id = p.m药材表id
    • (3)索引

    • (4)分析

    2张虚拟表left join

    使用了优化器为派生表生成的索引

    这边比较浪费性能,每次查询,都要对医生历史开方记录进行统计,并且统计还是几张大表计算后的结果。但是如果只是sql优化,这边暂时无法优化。

    12.2-1

    • (1)Explain
    1,PRIMARY,<derived7>,ALL,NULL,NULL,NULL,NULL,3096,Using where; Using temporary; Using filesort
    • (2)Sql
    • (3)索引
    • (4)分析

    临时表操作

    13.3-1

    • (1)Explain
    1,PRIMARY,<derived2>,ref,<auto_key0>,<auto_key0>,4,msu.药材表id,29,NULL
    • (2)Sql
    • (3)索引
    • (4)分析

    临时表操作

    14.null

    • (1)Explain
    NULL,UNION RESULT,"<union4,5>",ALL,NULL,NULL,NULL,NULL,NULL,Using temporary
    • (2)Sql
    • (3)索引
    • (4)分析

    临时表

    (二)优化sql

    上面我们只做索引的优化,遵循的原则是:

    1.最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
    
    2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
    
    3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。
    
    4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)。
    
    5.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

    查询优化神器 - explain命令

    关于explain命令相信大家并不陌生,具体用法和字段含义可以参考官网explain-output,这里需要强调rows是核心指标,绝大部分rows小的语句执行一定很快(有例外,下面会讲到)。所以优化语句基本上都是在优化rows。

    化基本步骤:

    0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
    
    1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高;
    
    2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询);
    
    3.order by limit 形式的sql语句让排序的表优先查;
    
    4.了解业务方使用场景;
    
    5.加索引时参照建索引的几大原则;
    
    6.观察结果,不符合预期继续从0分析;
    

    上面已经详细的分析了每一个步骤,根据上面的sql,去除union操作, 增加索引。可以看出,优化后虽然有所改善。但是距离我们的希望还有很大距离,但是光做sql优化,感觉也没有多少改进空间,所以决定从其他方面解决。

    (三)拆分sql

       由于速度还是不领人满意,尤其是个人用药情况统计,其实没必要每次都全部统计一次,再要优化,只靠修改索引应该是不行的了,所以考虑使用缓存。

    接下来是修改php代码,把全部sql语句拆分,然后再组装。

    • (1)搜索真名,别名(缓存)
    SELECT  mb.id,  mb.sort_id,  mb.title,  mb.py,  mb.unit,  mb.weight,  mb.tid,  mb.amount_max,  mb.poisonous,  mb.is_auxiliary,  mb.is_auxiliary_free,  mb.is_difficult_powder,  mb.brief,  mb.is_fixed_recipe,  IFNULL(group_concat(malias.title),'') atitle,  IFNULL(group_concat(malias.py),'') apy  FROM  药材表 AS mb  LEFT JOIN 药材表 AS malias ON malias.tid = mb.id  WHERE  mb.tid = 0  AND (  malias.title LIKE '%GC%'  OR malias.py LIKE '%GC%'  or mb.title LIKE '%GC%'  OR mb.py LIKE '%GC%'  )  group by  mb.id

    • (2)如果命中有药材
    ①排序

    真名在前,别名在后,完全匹配在前,部分匹配在后

    //对搜索结果进行处理,增加权重
    ②对供应商药材搜索
    SELECT ms.药材表id, max( ms.audit ) AS audit, max( ms.price ) AS price, max( ms.market_price ) AS market_price, max( ms.is_granule ) AS is_granule, max( ms.is_decoct ) AS is_decoct, max( ms.is_slice ) AS is_slice, max( ms.is_cream ) AS is_cream, max( ms.is_extract ) AS is_extract, max( ms.is_cream_granule) AS is_cream_granule, max( ms.is_extract_granule) AS is_extract_granule, max( ms.is_drychip ) AS is_drychip, max( ms.is_pill ) AS is_pill, max( ms.is_powder ) AS is_powder, max( ms.is_bolus ) AS is_bolus  FROM 供应商药材表 AS ms WHERE ms.audit = 1 AND ms.供应商idin (  SELECT DISTINCT  ( ssof.供应商id) AS 供应商id FROM  药库供应商关系表 AS ssof  WHERE  ssof.药库id IN ( 1,2,8,9,10,11,12,13,14,15,17,22,24,25,26,27,31,33 )  AND ssof.药方剂型id IN (1) ) AND ms.药材表id IN ( 78,205,206,207,208,209,334,356,397,416,584,652,988,3001,3200,3248,3521,3522,3599,3610,3624,4395,4396,4397,4398,4399,4400,4401,4402,4403,4404,4405,4406,4407,4408,5704,5705,5706,5739,5740,5741,5742,5743,6265,6266,6267,6268,6514,6515,6516,6517,6518,6742,6743 ) AND ms.is_slice = 1  GROUP BY ms.药材表id  

    ③拿医生历史开方药材用量数据(缓存)
    SELECT  count( * ) AS total,  rc.i AS 药材表id FROM  处方药材表 AS rc  INNER JOIN 药方表AS r ON r.id = rc.药方表_id WHERE  r.did = 40  AND r.timeline > 1576116927  AND rc.type_id in (1,3) GROUP BY  rc.i

    ④  装配及排序微调

    • (3)小结

    运行速度,对于开方量不是特别多的医生来说,两者速度都是0.1秒左右.但是如果碰到开方量大的医生,优化后的sql速度比较稳定,能始终维持在0.1秒左右,优化前的sql速度会超过0.2秒.速度提升约一倍以上。 

    最后对搜索结果和未优化前的搜索结果进行比对,结果数量和顺序完全一致.本次优化结束。

    四、附录:

    
    SELECT sql_no_cache 
        *
    FROM
        (
            -- mbu start##
            SELECT
                m.*,
                ifnull(p.total, 0) AS total
            FROM
                (
                    --
    全部实名药材
    开始
    ##
    SELECT
           munion.id,
           munion.sort_id,
           case when length(
            trim(
                  group_concat(munion.atitle SEPARATOR ' ')
                     )
                 )> 0 then concat(
               munion.title,
              '(',
          trim(
                 group_concat(munion.atitle SEPARATOR ' ')
                  ),
                    ')'
                 ) else munion.title end as title,
            munion.py,
            munion.unit,
            munion.weight,
            munion.tid,
            munion.amount_max,
            munion.poisonous,
            munion.is_auxiliary,
            munion.is_auxiliary_free,
            munion.is_difficult_powder,
            munion.brief,
            munion.is_fixed_recipe,
            --  trim( group_concat( munion.atitle SEPARATOR ' ' ) ) AS atitle,##
            --  trim( group_concat( munion.apy SEPARATOR  ' ' ) ) AS apy,##
                  max(ttid) * 100000 + id AS ttid
               FROM
                  (
                    -- #union start
    联合查找
    ,
    得到全部药材
    ##
      (
           SELECT
                  mb.id,
                  mb.sort_id,
                  mb.title,
                  mb.py,
                  mb.unit,
                  mb.weight,
                  mb.tid,
                  mb.amount_max,
                  mb.poisonous,
                  mb.is_auxiliary,
                  mb.is_auxiliary_free,
                  mb.is_difficult_powder,
                  mb.brief,
                  mb.is_fixed_recipe,
                  '' AS atitle,
                  '' AS apy,
                  CASE WHEN mb.py = 'GC' THEN 3 ELSE CASE WHEN mb.title = 'GC' THEN 3 ELSE 1 END END AS ttid
                   FROM
                     药材表 AS mb
                         WHERE
                             mb.tid = 0
                           AND (
                                  mb.title LIKE '%GC%'
                                  OR mb.py LIKE '%GC%'
                                    )
                            ) --
    真名药材
     
    结束
    ##
     UNION ALL
        (
          SELECT
                mb.id,
                mb.sort_id,
                mb.title,
                mb.py,
                mb.unit,
                mb.weight,
                mb.tid,
                mb.amount_max,
                mb.poisonous,
                mb.is_auxiliary,
                mb.is_auxiliary_free,
                mb.is_difficult_powder,
                mb.brief,
                mb.is_fixed_recipe,
                CASE WHEN malias.py = 'GC' THEN malias.title ELSE CASE WHEN malias.title = 'GC' THEN malias.title ELSE '' END END AS atitle,
                malias.py AS apy,
                CASE WHEN malias.py = 'GC' THEN 2 ELSE CASE WHEN malias.title = 'GC' THEN 2 ELSE 1 END END AS ttid
              FROM
                    药材表 AS mb
                    LEFT JOIN 药材表 AS malias ON malias.tid = mb.id
              WHERE
                    malias.title LIKE '%GC%'
                    OR malias.py LIKE '%GC%'
                          ) --
    其他药材结束
    ##
                     -- #union end##
                    ) munion
                    GROUP BY
                        id --
    全部实名药材
     
    结束
    ##
                        ) m
                LEFT JOIN (
                    --
    个人使用药材统计
     
    开始
    ##
        SELECT
              count(*) AS total,
              rc.i AS m药材表id
         FROM
               处方药材表 AS rc
               INNER JOIN 药方表AS r ON r.id = rc.药方表_id
          WHERE
               r.did = 40
                AND r.timeline > 1576115196
                AND rc.type_id in (1, 3)
           GROUP BY
                  rc.i --
    个人使用药材统计
     
    结束
    ##
               ) p ON m.id = p.m药材表id -- mbu end ##
                ) mbu
        INNER JOIN (
            -- msu start
    供应商药材筛选
    ##
            SELECT
                ms.药材表id,
                max(ms.audit) AS audit,
                max(ms.price) AS price,
                max(ms.market_price) AS market_price,
                max(ms.is_granule) AS is_granule,
                max(ms.is_decoct) AS is_decoct,
                max(ms.is_slice) AS is_slice,
                max(ms.is_cream) AS is_cream,
                max(ms.is_extract) AS is_extract,
                max(ms.is_cream_granule) AS is_cream_granule,
                max(ms.is_extract_granule) AS is_extract_granule,
                max(ms.is_drychip) AS is_drychip,
                max(ms.is_pill) AS is_pill,
                max(ms.is_powder) AS is_powder,
                max(ms.is_bolus) AS is_bolus
            FROM
                供应商药材表 AS ms
                INNER JOIN (
                    SELECT
                        DISTINCT (ssof.供应商id) AS 供应商id
                    FROM
                        药库供应商关系表 AS ssof
                    WHERE
                        ssof.药库id IN (
                            1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 17, 22,
                            24, 25, 26, 27, 31, 33
                        )
                        AND ssof.药方剂型id IN (1)
                ) tp ON tp.供应商id= ms.供应商id
            WHERE
                ms.audit = 1
            GROUP BY
                ms.药材表id -- msu end ##
                ) msu ON mbu.id = msu.药材表id
    WHERE
        msu.药材表id > 0
        AND msu.is_slice = 1
    order by
        total desc,
        ttid desc
    ]]>
    开放、普惠、高性能-SLS时序存储助力打造企业级全方位监控方案-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

    无所不在的时序数据

    时间带走一切,长年累月会把你的名字、外貌、性格、命运都改变。 ---柏拉图


    随着时间的推移,万事万物都在不停的变化,而我们也会用各种数字去衡量这些变化信息,比如年龄、重量、速度、温度、金钱...在数字化时代中,我们会把这些随着时间变化的数据保存起来,挖掘这些数据的价值。通常我们会称这类数据为---时序数据。

    时序数据用于描述物体在时间维度上的状态变化信息。

    image.png

    时序数据在各行各业都得到了非常广泛的应用,例如股票走势、交易趋势、服务器指标、脉搏心跳、定位坐标、能耗趋势等等,而这些数据几乎在所有的场景中都得到了应用,例如:

    1. 各类炒股软件提供众多不同维度的股票K线图,为广大股民提供参考标准;
    2. Apple Watch通过监控佩戴者的心率信息,帮助人们提早发现严重的心脏疾病;
    3. 国家电网通过分析各个小区、住户的用电量曲线,来判断是否有偷电漏电情况;
    4. 电商类的公司会监控平台的下单、交易、退货、评价等关键流程的变化趋势,用来快速发现各类异常;
    5. 各个游戏平台通过分析每个用户角色的操作、位置等变化规律,来判断是否使用了作弊辅助工具...

    我们需要一个什么样的时序存储

    为了能够支撑各种场景的时序分析、监控等需求,近几年在开源和商业领域均出现了一些时序存储的引擎,例如TimescaleDB、CrateDB、InfluxDB、OpenTSDB、Prometheus等,这些存储引擎分别有自己的生态和适用场景,在某些场景下具有较高的优势,例如TimescaleDB基于PostgreSQL,如果是PG的用户可以快速上手;InfluxDB具有非常完善的生态,TICK(Telegraf、 InfluxDB、Chronograf、Kapacitor)可以快速上手;Prometheus对于云原生场景支持非常友好,PromQL也非常便捷灵活,已经成为Kubernetes上监控的实事标准。


    然而从实际公司的业务场景出发,对于时序数据会有更多的要求:

    1. 高性能:时序数据通常流量大、保存周期长且需要长时间的范围查询,能够支撑大规模的写入与快速查询是必备条件;
    2. 开放:通常公司内部会有多个部门对不同系统的时序数据进行不同类型的分析、监控等需求,因此时序存储需具备足够的开放能力,支持各种数据的接入以及下游消费;
    3. 低成本:这里的成本主要包括两方面:资源成本和人力运维成本。有摩尔定律的存在,单位资源成本越来越低,而单位人力成本每年都在提升,因此低成本的核心在于运维这套时序存储的人力成本;
    4. 智能化:尤其在海量监控对象的场景中,纯粹的静态规则很难发现某个监控对象的异常,因此时序存储上层需要附加智能化的算法,提升监控的准确率。

    SLS时序存储发布

    SLS的日志存储引擎在2016年对外发布,目前承接阿里内部以及众多企业的日志数据存储,每天有数十PB的日志类数据写入。其中有很大一部分属于时序类数据或者用来计算时序指标,为了让用户能够一站式完成整个DevOps生命周期的数据接入、清洗、加工、提取、存储、可视化、监控、问题分析等过程,我们专门推出了时序存储的功能,与日志存储一道为大家解决各类机器数据的存储问题。


    image.png

    时序存储整体架构如上图所示,接入层可以对接各类开源的采集软件以及SLS自己开发的高性能Logtail,同时支持各种语言SDK直接写入,也支持Kafka、Syslog等开放性协议;存储层是完全分布式架构,每个时序库可通过Sharding方式水平扩展,数据默认3副本高可靠存储;计算层与存储层分离,提供SQL、PromQL纯分析型语法,同时提供智能分析能力。基于SLS提供的采集、存储、分析等功能可快速构建企业自己的业务监控、微服务监控等方案。

    功能特点

    image.png

    SLS时序存储从设计之初就是为了解决阿里内部与众多头部企业客户的时序存储需求,并借助于阿里内部多年的技术积累,使之可以适应绝大部分企业级时序监控/分析诉求。SLS时序存储的特点主要有:

    1. 丰富上下游:数据接入上SLS支持众多采集方式,包括各类开源Agent以及阿里云内部的监控数据通道;同时存储的时序数据支持对接各类的流计算、离线计算引擎,数据完全开放;
    2. 高性能:SLS存储计算分离架构充分发挥集群能力,尤其在大量数据下端对端的速度提升显著;
    3. 免运维:SLS的时序存储完全是服务化,无需用户自己去运维实例,而且所有数据都是3副本高可靠存储,不用担心数据的可靠性问题;
    4. 开源友好:SLS的时序存储原生支持Prometheus的写入和查询,并支持SQL92的分析方法,可以原生对接Grafana等可视化方案;
    5. 智能:SLS提供了各种AIOps算法,例如多周期估算、预测、异常检测、时序分类等各类时序算法,可以基于这些算法快速构建适应于公司业务的智能报警、诊断平台。

    典型应用场景

    应用/业务监控

    image.png

    应用/业务监控是公司层面重要的工作之一,在阿里内部一直作为最重要的监控项在建设。通过SLS提供的各类数据采集功能将所有应用/业务数据统一、实时采集,利用数据加工把各个不同时期、不同风格的数据做结构化处理,基于结构化的数据就可以做一定的分析,但通常业务数据量级较大,我们还会使用SQL的聚合功能对数据进行一定的降维,使用降维后的聚合时序数据来做告警以及长期的监控指标回溯。

    云原生监控

    image.png

    随着云原生技术的普及,越来越多的公司开始技术转型到云原生架构,借助于Prometheus、OpenTelemetry等CNCF的开源Project可以快速采集到Kubernetes以及各类中间件、应用的监控信息,阿里云上的云监控获取到所有云产品的监控数据。利用SLS时序存储以及日志/Trace存储的能力,可以支持各类监控数据的统一存储,数据可无缝对接Grafana的可视化,在Grafana上构建基础设施、云产品、中间件、应用软件的全方位监控大盘。

    访问日志分析

    image.png

    访问日志作为网站、APP的入口流量记录,能够直接反映出当前应用是否正常,因此运维领域的必备监控项。通过Logtail采集原始的访问日志,可用来分析/调查每个用户的请求,也可用作归档/审计需求;但原始访问日志量较大,不太适合直接的监控,通常会通过预聚合的方式对数据进行降维,基于聚合后的时序数据进行实时监控,并可应用SLS提供的智能巡检功能对每个业务站点进行独立的智能监控。


    大家在使用SLS中遇到的任何问题,请加钉钉群,我们有专门的日志女仆24小时在线答疑,还有火锅哥和烧烤哥专业支持!~ SLS微信公众号定期会发布各类日志、监控领域的技术分享文章并定期举行抽奖,欢迎小伙伴们关注~


    另外欢迎对大数据、分布式、机器学习等有兴趣的同学加入,转岗、内推,来者不拒,请用简历狠狠的砸我,联系邮箱 davidzhang.zc@alibaba-inc.com !~
    image.png


    ]]>
    数据加工 - 日志富化实战-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 数据加工介绍

    数据加工是阿里云日志服务推出的一项功能,用于对结构化或非结构化日志进行实时ETL行处理。目前该功能包含了200+的算子,本文从富化场景出发,介绍如何在数据加工过程中使用富化函数。
    PS: 我们这里讲的富化,对应的是SQL ETL场景的join

    图片.png

    数据加工的入口:
    打开sls日志服务,选择相关logstore的查询页面后,可以看到有一个 “数据加工”的开关,打开这个开关就可以写数据加工的代码了。
    图片.png
    数据加工函数总览:http://help.aliyun.com/document_detail/159702.html

    场景设定

    本文以Nginx日志 http code富化为例抛砖引玉,帮助大家熟悉数据加工中的日志富化方法
    http返回码在访问日志中比较常见,将返回码富化,可以让我们更直观地看到每个请求的状态,做更多统计工作。

    图片.png

    下面是常见的http code码含义的映射表
    图片.png

    使用数据加工 进行日志富化

    方式1 - 使用res_local 高级参数

    假设,我们富化的数据是一个csv 保存了code的映射关系

    code,alias,category,description
    100,1xx,Informational,Continue
    101,1xx,Informational,Switching Protocols
    ...

    将code的映射关系保存为数据加工的高级参数,key为 http_code, 值为csv文件内容

    e_table_map(tab_parse_csv("code,alias,category,descriptionn100,1xx,Informational,Continuen101,1xx,Informational,Switching Protocolsn102,1xx,Informational,Processing (WebDAV)n200,2xx,Success,OKn201,2xx,Success,Createdn202,2xx,Success,Acceptedn203,2xx,Success,Non-Authoritative Informationn204,2xx,Success,No Contentn205,2xx,Success,Reset Contentn206,2xx,Success,Partial Contentn207,2xx,Success,Multi-Status (WebDAV)n208,2xx,Success,Already Reported (WebDAV)n226,2xx,Success,IM Usedn300,3xx,Redirection,Multiple Choicesn301,3xx,Redirection,Moved Permanentlyn302,3xx,Redirection,Foundn303,3xx,Redirection,See Othern304,3xx,Redirection,Not Modifiedn305,3xx,Redirection,Use Proxyn306,3xx,Redirection,(Unused)n307,3xx,Redirection,Temporary Redirectn308,3xx,Redirection,Permanent Redirect (experimental)n400,4xx,Client Error,Bad Requestn401,4xx,Client Error,Unauthorizedn402,4xx,Client Error,Payment Requiredn403,4xx,Client Error,Forbiddenn404,4xx,Client Error,Not Foundn405,4xx,Client Error,Method Not Allowedn406,4xx,Client Error,Not Acceptablen407,4xx,Client Error,Proxy Authentication Requiredn408,4xx,Client Error,Request Timeoutn409,4xx,Client Error,Conflictn410,4xx,Client Error,Gonen411,4xx,Client Error,Length Requiredn412,4xx,Client Error,Precondition Failedn413,4xx,Client Error,Request Entity Too Largen414,4xx,Client Error,Request-URI Too Longn415,4xx,Client Error,Unsupported Media Typen416,4xx,Client Error,Requested Range Not Satisfiablen417,4xx,Client Error,Expectation Failedn418,4xx,Client Error,I'm a teapot (RFC 2324)n420,4xx,Client Error,Enhance Your Calm (Twitter)n422,4xx,Client Error,Unprocessable Entity (WebDAV)n423,4xx,Client Error,Locked (WebDAV)n424,4xx,Client Error,Failed Dependency (WebDAV)n425,4xx,Client Error,Reserved for WebDAVn426,4xx,Client Error,Upgrade Requiredn428,4xx,Client Error,Precondition Requiredn429,4xx,Client Error,Too Many Requestsn431,4xx,Client Error,Request Header Fields Too Largen444,4xx,Client Error,No Response (Nginx)n449,4xx,Client Error,Retry With (Microsoft)n450,4xx,Client Error,Blocked by Windows Parental Controls (Microsoft)n451,4xx,Client Error,Unavailable For Legal Reasonsn499,4xx,Client Error,Client Closed Request (Nginx)n500,5xx,Server Error,Internal Server Errorn501,5xx,Server Error,Not Implementedn502,5xx,Server Error,Bad Gatewayn503,5xx,Server Error,Service Unavailablen504,5xx,Server Error,Gateway Timeoutn505,5xx,Server Error,HTTP Version Not Supportedn506,5xx,Server Error,Variant Also Negotiates (Experimental)n507,5xx,Server Error,Insufficient Storage (WebDAV)n508,5xx,Server Error,Loop Detected (WebDAV)n509,5xx,Server Error,Bandwidth Limit Exceeded (Apache)n510,5xx,Server Error,Not Extendedn511,5xx,Server Error,Network Authentication Requiredn598,5xx,Server Error,Network read timeout errorn599,5xx,Server Error,Network connect timeout errorn"),
                  [("http_code","code")],
                  [("alias","http_code_alias"), ("description","http_code_desc"), 
                  ("category","http_code_category")])

    效果:
    图片.png

    方式2 - 通过使用OSS文件实现富化

    假设,我们的http code映射关系存在一个文件里。格式如下:

    code,alias,category,description
    100,1xx,Informational,Continue
    101,1xx,Informational,Switching Protocols
    ...

    上传 http_code.csv文件到oss
    打开OSS控制台 http://oss.console.aliyun.com
    找到已有的bucket或者新建一个bucket,根据控制台指引上传文件

    图片.png

    使用加工,富化

    e_table_map(
          tab_parse_csv(
               res_oss_file(endpoint="oss-cn-shanghai-internal.aliyuncs.com",
                  ak_id=res_local("AK_ID"), ak_key=res_local("AK_KEY"), 
                  bucket="ali-sls-etl-test", 
                  file="http_code.csv", format='text')),
                  [("http_code","code")],
                  [("alias","http_code_alias"),
                   ("description","http_code_desc"),
                   ("category","http_code_category")])

    res_local 引用的值需要在高级参数里定义。

    图片.png
    效果:

    图片.png

    方式3 - 通过MySQL 表实现富化

    假设,我们的http_code映射关系存在一个mysql表里
    图片.png

    使用加工,富化

    e_table_map(res_rds_mysql(address="MySQL主机地址", 
                      username="用户名", password="密码",
                      database="数据库",table="表名", refresh_interval=300),
                  [("http_code","code")],
                  [("alias","http_code_alias"), ("description","http_code_desc"), 
                  ("category","http_code_category")])
                  

    注意: 数据加工支持vpc方法方式rds,配置vpc打通可以参考:https://help.aliyun.com/document_detail/162753.html
    效果:

    图片.png

    总结

    整体流程

    图片.png

    方法比较

    图片.png

    限制:所有维表限制在2G

    参考材料

    https://help.aliyun.com/document_detail/125489.html 富化函数
    https://help.aliyun.com/document_detail/129401.html 资源函数
    https://www.restapitutorial.com/httpstatuscodes.html http code码

    ]]>
    Redis学习笔记之集群重启和遇到的坑-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 重启集群的步骤

    以我的理解,Redis集群重启分2种:
    1.普通重启,继续保留现有集群结构和持久化数据。
    2.破坏重启,破坏掉集群结构和删除持久化数据,重启后相当于重新创建集群。
    当然,通常我们肯定用的是第一种了。那么集群的重启步骤也很简单,就是先关闭再开启,下面以我的测试环境来举例。
    一主一从,共6节点,都部署在一台服务器上。
    1.普通重启
    关闭集群:

    redis-cli -c -h 192.168.211.131 -p 7000 shutdown
    redis-cli -c -h 192.168.211.131 -p 7001 shutdown
    redis-cli -c -h 192.168.211.131 -p 7002 shutdown
    redis-cli -c -h 192.168.211.131 -p 7003 shutdown
    redis-cli -c -h 192.168.211.131 -p 7004 shutdown
    redis-cli -c -h 192.168.211.131 -p 7005 shutdown

    启动集群:

    redis-server /redis/redis-6.0.6/cluster/7000/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7001/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7002/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7003/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7004/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7005/redis.conf

    2.破坏重启
    关闭集群:

    redis-cli -c -h 192.168.211.131 -p 7000 shutdown
    redis-cli -c -h 192.168.211.131 -p 7001 shutdown
    redis-cli -c -h 192.168.211.131 -p 7002 shutdown
    redis-cli -c -h 192.168.211.131 -p 7003 shutdown
    redis-cli -c -h 192.168.211.131 -p 7004 shutdown
    redis-cli -c -h 192.168.211.131 -p 7005 shutdown

    删除持久化文件、日志文件和集群配置文件

    rm -f append.aof dump.rdb redis.log nodes*.conf

    启动集群:

    redis-server /redis/redis-6.0.6/cluster/7000/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7001/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7002/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7003/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7004/redis.conf
    redis-server /redis/redis-6.0.6/cluster/7005/redis.conf

    由于现有集群结构被破坏了,所以还要创建集群

    redis-cli --cluster create 192.168.211.131:7000 192.168.211.131:7001
    192.168.211.131:7002 192.168.211.131:7003 192.168.211.131:7004
    192.168.211.131:7005 --cluster-replicas 1

    这样一个新的集群就创建好了

    在知道了集群重启的步骤后,来说下遇到的坑。

    第一个坑

    在自己的测试环境,一主一从,共6节点,都部署在一台服务器上,Redis开启了持久化,在重启集群后,发现有数据丢失,查看日志也没有发现错误,接下来在查看AOF文件和RDB文件后发现了问题,的确是有数据丢失,而且少的还是只有节点7001上的数据,这就很奇怪了,当时猜测是否是由于持久化文件在节点之间不共享,7001节点的文件被7000的覆盖掉了。为了验证这个推断,修改了appendfilename和dbfilename参数,每个节点文件名唯一。之后创建新数据,再次重启,这次没有发现数据丢失,所有数据都存在。
    简单模拟下这个问题:
    修改6个节点的持久化文件参数,使用默认参数

    appendfilename appendonly.aof
    dbfilename dump.rdb

    启动Redis集群,新加数据
    图片.png
    重启集群
    可以看出有的数据丢失了
    图片.png
    其实持久化的数据是可以在AOF文件中看到的,下面2个节点的AOF文件中的键值是相同的,可以大致推断出是一主一从节点。
    图片.png
    图片.png
    从cluster nodes的结果来看,也能验证。
    图片.png

    第二个坑

    redis.conf中的dir参数,这个参数配置的是工作目录,包括指定AOF文件和RDB文件的路径,如果不配置,那么RDB文件和AOF文件就会在执行redis-server启动命令的路径下创建。这样就会导致在重启后无法正确加载持久化文件,所以dir参数一定要配置。

    ]]>
    你分得清MySQL普通索引和唯一索引了吗?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 0 概念区分
    • 普通索引和唯一索引
      普通索引可以重复,唯一索引和主键一样不能重复。

    唯一索引可以作为数据的一个合法验证手段,例如学生表的身份证号码字段,我们人为规定该字段不得重复,那么就使用唯一索引。(一般设置学号字段为主键)

    • 主键和唯一索引
      主键保证数据库里面的每一行都是唯一的,比如身份证,学号等,在表中要求唯一,不重复。唯一索引的作用跟主键的作用一样。

    不同的是,在一张表里面只能有一个主键,主键不能为空,唯一索引可以有多个,唯一索引可以有一条记录为空,即保证跟别人不一样就行。
    比如学生表,在学校里面一般用学号做主键,身份证则弄成唯一索引;而到了教育局,他们就把身份证号弄成主键,学号换成了唯一索引。
    选谁做表的主键,要看实际应用,主键不能为空。

    1 示例

    一个市民系统,每个人都有个唯一身份证号;
    业务代码已保证不会写入两个重复的身份证号;
    如果市民系统需要按照身份证号查姓名,就会执行类似SQL:

    select name from CUser where id_card = 'xxxxxxxyyyyyyzzzzz';

    相信你一定会在id_card字段上建索引。

    由于身份证号字段比较大,不建推荐把身份证号做主键。
    因此现在有两个选择

    1. 给id_card字段创建唯一索引
    2. 创建一个普通索引

    如果业务代码已保证不会写入重复的身份证号,那这两个选择逻辑上都正确。

    但从性能角度考虑,唯一索引还是普通索引呢?
    假设字段 k 上的值都不重复。

    • InnoDB的索引组织结构
      接下来从这两种索引对查询语句和更新语句的性能影响来进行分析。

    2 查询过程

    查询语句

    select id from T where k=5

    该语句在索引树查找的过程:
    先通过B+树从树根开始,按层搜索到叶节点,即图中右下角的数据页,然后可认为数据页内部是通过二分法定位记录。

    • 对普通索引,查找到满足条件的第一个记录(5,500)后,需查找下个记录,直到碰到第一个不满足k=5条件的记录
    • 对唯一索引,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止检索。

    该不同点带来的性能差距会有多少呢?
    微乎其微!

    InnoDB数据是按数据页为单位读写。即当需读一条记录时,并非将该记录本身从磁盘读出,而是以页为单位,将其整体读入内存。

    InnoDB中,每个数据页的大小默认是16KB。

    因引擎按页读写,所以,当找到k=5记录时,它所在数据页就都在内存了。
    对普通索引,要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。
    如果k=5记录刚好是该数据页的最后一个记录,那么要取下个记录,必须读取下个数据页,操作会稍微复杂。
    对于整型字段,一个数据页可存近千个key,因此这种情况概率很低。所以,计算平均性能差异时,仍可认为该操作成本对现在的CPU可忽略不计。

    3 更新过程

    3.1 change buffer

    需更新一个数据页时

    • 若数据页在内存,直接更新
    • 若该数据页不在内存,在不影响数据一致性前提下,InooDB会将这些更新操作缓存在change buffer,无需从磁盘读入该数据页。
      在下次查询需要访问该数据页时,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过该方式就能保证这个数据逻辑的正确性。

    虽然叫change buffer,实际上是可持久化的数据。
    即change buffer在内存中有拷贝,也会被写进磁盘。

    3.2 merge

    3.2.1 概念

    将change buffer中的操作应用到原数据页,得到最新结果的过程。

    3.2.2 触发时机

    访问该数据页会触发merge
    系统有后台线程会定期merge
    在数据库正常关闭(shutdown)的过程中,也会执行merge。

    若能将更新操作先记录在change buffer,减少读盘,语句执行速度会明显提升。
    且数据读入内存需要占用buffer pool,所以该方式还能避免占用内存,提高内存利用率。

    3.3 何时用change buffer

    对于唯一索引,所有更新操作要先判断该操作是否违反唯一性约束。

    比如,要插入(4,400)记录,要先判断表中是否已存k=4记录,而这必须要将数据页读入内存才能判断。
    如果都已经读入到内存,那直接更新内存会更快,就没必要使用change buffer。
    因此,唯一索引的更新就不能使用change buffer,实际上也只有普通索引可使用

    change buffer用的是buffer pool里的内存,因此不能无限增大。
    change buffer的大小,可通过参数innodb_change_buffer_max_size动态设置。
    参数设置为50时,表示change buffer的大小最多只能占用buffer pool的50%。

    理解了change buffer机制,看看要在这张表中插入一个新记录(4,400),InnoDB处理流程。

    分情况讨论该记录要更新的目标页是否在内存中:

    在内存

    • 唯一索引
      找到3和5之间位置,判断到没有冲突,插入值,语句执行结束。
    • 普通索引
      找到3和5之间位置,插入值,语句执行结束。

    普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小CPU时间。

    不在内存

    • 唯一索引
      需要将数据页读入内存,判断到没有冲突,插入值,语句执行结束
    • 普通索引
      将更新记录在change buffer,语句执行结束

    将数据从磁盘读入内存涉及随机IO访问,是数据库里面成本最高操作之一。
    change buffer因减少随机磁盘访问,所以对更新性能提升明显。

    问题案例:某业务的库内存命中率突然从99%降低到了75%,整个系统处于阻塞状态,更新语句全部堵住。
    探究其原因,发现该业务有大量插入数据操作,而DBA在前天把其中的某个普通索引改成了唯一索引。

    change buffer的使用场景

    普通索引的所有场景,使用change buffer都可加速吗?

    因为merge才是真正进行数据更新时刻;
    change buffer主要目的是将记录的变更动作缓存下来;
    所以在一个数据页做merge前,change buffer记录变更越多(即该数据页上要更新的次数越多),收益越大。

    对写多读少业务,页面在写完后马上被访问到的概率较小,change buffer使用效果最好。该类业务模型常见为账单、日志类的系统。

    反之,假设一业务的更新模式是写后马上查询,那么即使满足条件,将更新先记录在change buffer,但之后由于马上要访问该数据页,立即触发merge。
    这样随机访问IO的次数不会减少,反而增加change buffer维护代价。
    所以,对于这种业务模式,change buffer起副作用。

    4 实践中的索引选择

    普通索引和唯一索引如何抉择。
    这两类索引在查询性能上没差别,主要考虑对更新性能影响。
    所以,推荐尽量选择普通索引。

    如果所有更新后面,都紧跟对该记录的查询,那么该关闭change buffer。
    而在其他情况下,change buffer都能提升更新性能。
    普通索引和change buffer的配合使用,对于数据量大的表的更新优化还是很明显的。

    在使用机械硬盘时,change buffer机制的收效非常显著。
    所以,当你有一个类似“历史数据”的库,并且出于成本考虑用机械硬盘时,应该关注这些表里的索引,尽量使用普通索引,把change buffer 开大,确保“历史数据”表的数据写速度。

    5 change buffer 和 redo log

    WAL 提升性能的核心机制,也是尽量减少随机读写,这两个概念易混淆。
    所以,这里我把它们放到了同一个流程里来说明区分。

    在表上

    5.1 执行插入

    insert into t(id,k) values(id1,k1),(id2,k2);

    假设当前k索引树的状态,查找到位置后
    k1所在数据页在内存(InnoDB buffer pool),k2所在的数据页不在内存中

    • 带change buffer的更新状态图。

    该更新语句涉及四部分:

    • 内存
    • redo log(ib_log_fileX)
    • 数据表空间(t.ibd)
    • 系统表空间(ibdata1)

    该更新语句做了如下操作(按图中数字顺序):

    1. Page1在内存,直接更新内存
    2. Page2没有在内存中,就在内存的change buffer区,记录下“我要往Page2插一行”的信息
    3. 将前两个动作记入redo log(图中的3和4)

    做完上面,事务完成。执行这条更新语句的成本很低,就写两处内存,然后写一处磁盘(两次操作合在一起写了一次磁盘),还是顺序写。

    图中两个虚箭,是后台操作,不影响更新的响应时间。

    这之后的读请求,怎么处理?
    现在执行

    select * from t where k in (k1, k2)

    若读语句紧随在更新语句后,内存中的数据都还在,那么此时这俩读操作就与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关。所以在图中就没画这俩。

    • 两个读请求的流程图(带change buffer的读过程)

    从图中可见:
    读Page1时,直接从内存返回。
    WAL之后如果读数据,是不是一定要读盘,是不是一定要从redo log里面把数据更新以后才可以返回?其实不用。
    看上图状态,虽然磁盘上还是之前数据,但这里直接从内存返回结果,结果正确。

    要读Page2时,需把Page2从磁盘读入内存,然后应用change buffer里面的操作日志,生成一个正确版本并返回结果。
    可见直到需读Page2时,该数据页才被读入内存。

    所以,要简单对比这俩机制对更新性能影响

    • redo log 主要节省随机写磁盘的IO消耗(转成顺序写)
    • change buffer主要节省随机读磁盘的IO消耗

    6 总结

    由于唯一索引用不了change buffer的优化机制,因此如果业务可以接受,从性能角度,推荐优先考虑非唯一索引。

    6.1 关于到底是否使用唯一索引

    主要纠结在“业务可能无法确保”。本文前提是“业务代码已经保证不会写入重复数据”下,讨论性能问题。

    • 如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。这种情况下,本文意义在于,如果碰上大量插入数据慢、内存命中率低时,多提供一个排查思路。
    • 然后,在一些“归档库”的场景,可考虑使用唯一索引的。比如,线上数据只需保留半年,然后历史数据保存在归档库。此时,归档数据已是确保没有唯一键冲突。要提高归档效率,可考虑把表的唯一索引改普通索引。

    6.2 如果某次写入使用change buffer,之后主机异常重启,是否会丢失change buffer的数据?

    不会丢失。
    虽然是只更新内存,但在事务提交时,我们把change buffer的操作也记录到redo log,所以崩溃恢复时,change buffer也能找回。

    6.3 merge的过程是否会把数据直接写回磁盘?

    merge执行流程

    1. 从磁盘读入数据页到内存(老版本数据页)
    2. 从change buffer找出该数据页的change buffer 记录(可能有多个),依次应用,得到新版数据页
    3. 写redo log
      该redo log包含数据的变更和change buffer的变更

    至此merge过程结束。
    这时,数据页和内存中change buffer对应磁盘位置都尚未修改,是脏页,之后各自刷回自己物理数据,就是另外一过程。

    ]]>
    Oracle运维笔记之IMPDP导入数据报错ORA-00943-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在做impdp导入表数据时,出现了ORA-00943:cluster does not exist的错误,通常在导入数据报错都是表已存在、用户名或表空间不存在等问题,这个错误很奇怪,之前从来没有遇到过。
    图片.png
    在MOS上搜索时发现了文档ORA-943 When Importing Clusters (Doc ID 1005258.6),这个错误ORACLE是这样解释的:
    图片.png
    简单来说就是如果导入的表是聚簇表,而目标端预先创建的表不是聚簇表的话,那么在导入时表定义检查就会失败,报错ORA-00943: cluster does not exist。
    解决方案就是,创建聚簇再导入表数据。
    接下来就按照解决方案来处理
    1.查看表的聚簇,CLUSTER_EIS_STUDIES

    SQL> select owner,table_name,cluster_name from dba_tables where table_name='EISREPORTS';
    OWNER TABLE_NAME CLUSTER_NAME
    ------------------------------ ------------------------------ ---------------------------
    RADINFO EISREPORTS CLUSTER_EIS_STUDIES

    2.获取聚簇的创建语句

    select dbms_metadata.get_ddl('CLUSTER','CLUSTER_EIS_STUDIES','RADINFO') from dual;

    3.在目标库上创建聚簇

    CREATE CLUSTER RADINFO.CLUSTER_EIS_STUDIES (
    PATIENTSINDEX NUMBER(10,0) )
    SIZE 2048

    聚簇已经创建完毕,再次impdp导入数据,却发现仍然报错,但这次的错误和之前不同,ORA-02032: clustered tables cannot be used before the cluster index is built,
    从字面理解来说就是聚簇索引创建前无法使用聚簇表。
    图片.png
    那么接下来就要从源端查询聚簇索引信息,之后在目标端创建了。
    1.在源端查询聚簇索引信息:

    select index_owner,index_name,table_name,column_name from dba_ind_columns where table_name='CLUSTER_EIS_STUDIES'
    INDEX_OWNER INDEX_NAME TABLE_NAME COLUMN_NAME
    -------------------------- ------------------------------ -------------------------- --------------------------
    RADINFO IX_CLUSTER_EIS_STUDIES CLUSTER_EIS_STUDIES PATIENTSINDEX

    2.在目标端创建聚簇索引:

    create index radinfo.IX_CLUSTER_EIS_STUDIES on cluster CLUSTER_EIS_STUDIES;

    再次impdp导入数据,这次终于执行成功。

    ]]>
    后疫情时代,AnalyticDB如何助力企业实现业务增长和创新-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 数字经济时代,越来越多的企业通过数据驱动业务增长、流程优化及更多的业务创新。当前企业数据朝着海量、实时化、多样化的趋势演进,对企业数据仓库也提出了新的挑战与演进诉求。

    同时,数据正在发生质的变化,数据爆炸性增长,实时数据比例大幅增长,数据库在云上比例迅速上升,非结构化数据占比越来越高。数据业务也在面临转型,从传统的T+1到全实时,从离线到在离线一体化,数据从多源分散到集中处理,计算和存储也全面Serverless化

    企业需要怎样的数据仓库?

    数据的重要性不言而喻,但用户需要怎样的数据平台与服务却并不是三言两语就能说清的一件事。企业在数据仓库的解决方案中最需要的价值服务在哪里?

    1、性能(Performance)

    无论在什么行业,客户一定会要求:快!更快!性能是数据平台的准入门槛。如何在PB级别的海量数据量面前展现优秀的性能,同时尽可能的降低成本,是传统数据仓库的死穴。只有借助云端的资源才有可能在这个数量级上讨论这些问题。

    2、实时(Realtime)

    在self-BI的时代,终端用户不用通过IT生成报表,数据要求越发实时性,用户需要分析几小时甚至几分钟之前的数据,而不仅仅满足昨天的数据。

    3、高效(Efficient)

    国内互联网网民数和移动用户数饱和,人口红利消失,从增量市场进入存量市场,存量市场竞争更加激烈。在这种环境下,如何用最小的成本,挖掘最大的流量价值,简单、快速和高效为企业带来新的业务增长点,为大多数企业所追逐。

    阿里云“万仓计划”  助力每个企业拥有属于自己的云原生数据仓库

    阿里云“万仓计划” 以云原生数据仓库AnalyticDB为核心,在后疫情时代帮助企业低成本快速构建数据仓库,搭建一套良好的精细化运营和营销体系,助力企业营收快速增长。

    AnalyticDB融合了分布式、弹性计算与云计算的优势,在性能、实时、高效和规模四个方面具有极大的突破。云原生数据仓库AnalyticDB支持更大规模的并发访问、更快读写能力以及更智能的混合查询负载管理等,实现更精细化的资源利用和更低成本的投入,让用户能更加专注于业务发展,专注于数据价值。

    1、超大规模

    基于强一致RAFT协议的副本同步机制,最高每秒千万级的实时写入性能,支持最大100PB的存储空间。

    2、强劲性能

    轻量的索引构建方式、分布式混合计算引擎和优化器,具有承载更快和更复杂读写能力。ADB最新发布的性能白皮书显示,其性能相比MySQL有100倍的优势。数据仓库权威榜单:ADB包揽TPC-DS(数据仓库)、TPC-H
    再次刷新TPC-DS 10TB榜单成为全球第一,性能、性价比全球领先!相比前世界纪录Spark优化版,综合性能提升29%,单位成本仅为其1/3。相比前世界纪录 微软SQL Server 2019,综合性能提升290%,单位成本仅为其1/4,成为中国首次荣登该榜单的产品!

    3、灵活弹性

    基于存储计算分离架构,存储空间可以秒级扩容到100PB,计算资源快速从3节点升级到5000节点。ADB架构完美诠释了数据仓库产品的进化史,它被设计成为精准的制导导弹用于解决众多的历史遗留问题:花钱多、灵活度低和令人头疼的运营管理等。

    4、融合计算和分析

    随着移动互联网和智能化的快速发展,产生了大量的非结构数据,如何能快速挖掘海量的非结构化数据价值,尤其重要。ADB支持海量结构化和非结构化数据融合分析,在线分析和ETL计算一体化。实现了数据库和大数据一体化进程,可以帮助客户简单快速搭建数据仓库,使客户专心于业务上的开发以及业务价值提升。

    云原生数据仓库AnalyticDB经过八年阿里巴巴集团和云上客户实践,在性能和性价比方面全球领先,为世界上最快的云原生数据仓库。AnalyticDB利用云计算的壳解释了云原生数据仓库存在的合理性,是数据仓库的终极形态!我们有能力和实力助力每个企业拥有属于自己的云原生数据仓库,助力企业数据价值在线化!

    ]]>
    【其他】微消息队列 MQTT 升级通知 Fri, 20 Jun 2025 02:20:33 +0800

    【阿里云】【微消息队列MQTT】【版本升级通知】

    升级时间:2020年7月22日
    资源迁移时间:2020年8月31日前

    升级内容:
    为了进一步提升MQTT的客户体验,自 2020 年 7 月 22 日起,微消息队列 MQTT 版将启动从 V2.x.x 至 V3.x.x 的升级流程。我们优化了MQTT实例创建流程非必要绑定RocketMQ。同时提供更加容易理解且稳定的OpenAPI方便您的使用。升级过程不影响客户端消息收发。如果您对升级时间和窗口有特殊要求,请提交工单申请协商。
    微消息队列 MQTT的 V3.x.x 版本相比 V2.x.x 版本而言,实现了自身的消息独立存储,再也无需强制绑定消息队列 RocketMQ 版,因此购买开通微消息队列 MQTT 版实例后即可直接创建资源,从而降低您的使用与理解成本。
    V3.x.x 版本除了独立存储之外,还对 V2.x.x 版本实现的微消息队列 MQTT 版和消息队列 RocketMQ 版互通的功能进行抽象,使得微消息队列 MQTT 版的数据可以根据您配置相应规则转发到其他阿里云产品,也可以配置规则从其他阿里云产品导入数据到微消息队列 MQTT 版。消息队列 RocketMQ 版作为转发的其他云产品之一。
    详情参见《V2.x.x 到 V3.x.x 的版本升级说明
    升级影响:
    1. 创建实例流程变更,若仅使用MQTT收发消息,不需要您绑定RocketMQ。
    2.若您有MQTT与RocketMQ客户端交互的使用场景,请参见消息收发流程变更,及时配置您的规则。详情参见《消息收发流程变更链接

    3. V2.x.x 版本中如果使用了 OpenAPI 来创建 Topic、Group ID 资源,在服务端升级到 V3.x.x 版本后,V2.x.x 的 OpenAPI 将无法产生正确的效果,因此您需迁移到新的 OpenAPI 调用链路来实现资源初始化。若您已参照当前OpenAPI手册《OpenAPI手册链接使用》,则不受影响。若您的OpenAPI无法正常使用,可提工单咨询,我们将协助您解决。


    ]]>
    【北京乘云至达科技有限公司】- 招聘职位详情(校招/社招) Fri, 20 Jun 2025 02:20:33 +0800 北京乘云至达科技有限公司介绍

    乘云科技,是一家以提供云计算、云服务、大数据、专业IT服务产品研发、系统集成、IT战略咨询于一体的高新技术企业,乘云基于公共云计算技术,致力于为广大企业提供普惠的云计算、大数据产品。

    公司2014年成立,2016年成为阿里云合作伙伴,FY18财年完成公有云销售额1100万+、专有云销售额1000万+、完全All in阿里云业务,FY19年,成为阿里云授权服务中心、阿里云钻石级合作伙伴、阿里云一级城市服务商、阿里云邮箱业务华北区总代、阿里云专有云合作伙伴。

    职位1: 云计算运营经理

    Base地点: 北京/深圳
    岗位职责:
    1、负责云计算相关产品或解决方案的相关市场调研、竞品分析、产品设计规划、运营活动协调等工作;
    2、深刻理解技术合作伙伴及目标客户的业务需求、非功能需求、流程需求,并将需求落地为产品功能、平台流程;
    3、与技术支持、客服、运营、售前、销售以及客户等对接,评估产品迭代需求优先级,并组织产品经理落地产品功能地迭代;
    4、组合产品,形成客户应用场景,并配合推广,同时为结果负责;
    5、制定用户满意度调研以及nps问卷,输出结果并根据结果制定优化策略并执行;
    6、策划并组织形式多样的类目运营活动、产品优惠活动、线下沙龙活动等,包括线上、线下等方式;
    7、对用户使用体验,购买转化率,续费留存率等结果指标负责;
    8、具体岗位职责依据候选人背景情况定岗后确认。
    9、通过数据纬度支撑公司业务发展,推动业务团队完成既定的任务目标。
    具体要求:
    工作年限:不限
    学历:本科以上
    专业:信息相关专业
    1.本科及以上学历,信息类相关专业;
    2、熟悉云计算领域的产品、技术特点及技术架构;
    3、技术领域涉猎广泛,知识面广,并在某个领域的技术方案架构具有专长优势;
    4、具有娴熟的沟通技巧,执行力强,具有优秀的团队合作精神、敬业精神;
    5、具有产品设计、产品运营、用户满意度调研等经验;
    6、具有传统系统集成商,咨询公司,传统厂商,云服务商等解决方案、售前支持、咨询等经验者优先;
    7、具有云计算从业经验优先。
    其他要求:
    有良好的客户资源背景优先。有蓝讯、网宿、天地祥云、世纪互联、阿里云、金山云、AWS、百度云公司工作经验者优先。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:8人;持阿里云认证优先

    职位2:云计算销售经理(管理岗)

    Base地点: 北京/深圳
    岗位职责:
    1、根据销售目标,制定销售计划,管理销售团队,建立良好的客户关系与合作伙伴关系,确保经营任务的达成;
    2、挖掘商业机会,引导客户需求,整合内外部资源、进行销售拓展工作;
    3、开拓新市场,发展新客户,拓展公司业务领域;
    具体要求:
    工作年限:5年以上
    学历:
    专业:信息相关专业
    1、5年以上IT行业销售经验,2年以上团队管理经验、项目管理经验、对业绩目标具有很强的认知管理能力,独立的目标思维;
    2、形象良好,语言表达和思维逻辑清晰、性格乐观、能承受压力,品格坚毅;
    3、具有良好的沟通能力和团队协作精神。
    4、有阿里云和微软云销售经验的销售人员优先

    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:5人;持阿里云认证优先

    职位3: 云计算客户经理

    Base地点: 北京/深圳
    岗位职责:
    1、 云计算产品与服务的销售工作,努力挖掘潜在客户资源;
    2、 维护既有客户资源,保持客户沟通,确保客户继续使用公司服务,挖掘及激发客户发展需求,不断扩展业绩规模。
    具体要求:
    工作年限:不限
    学历:不限
    专业:信息相关专业
    1、 对云计算IaaS/PaaS/SaaS有一定了解;
    2、 公司倾向及培养售前、销售,技术型销售人才;
    3、 从事互联网、云计算、大数据、人工智能行业销售人员优先;
    4、 对IDC、网络、存储、DNS、CDN等要有一定的理解;
    5、 熟悉阿里云相关产品及相关技术概念,对其他云厂商有所了解;
    6、 有良好的沟通、销售、文档编写能力。
    其他要求:
    有良好的客户资源背景优先。有蓝讯、网宿、天地祥云、世纪互联、阿里云、金山云、AWS、百度云公司工作经验者优先。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:8人;持阿里云认证优先
    ]]>
    【福州睿长网络科技有限公司】- 招聘职位详情(校招/社招) Fri, 20 Jun 2025 02:20:33 +0800 福州睿长网络科技有限公司介绍

    福州睿长网络科技有限公司为阿里云区域一级合作伙伴,授权服务中心,区域赋能中心,成立以来,先后为两千余家企事业单位提供涵盖阿里云计算相关业务和技术支持,企事业单位移动信息化,软件开发,大数据中台等等一系列创新型高科技服务,为阿里云全国范围内最深度的合作伙伴之一。

    职位1: 数据资产梳理

    Base地点: 福州
    岗位职责:
    负责日常数据分析、数据筛选等运维工作
    具体要求:
    工作年限:不限
    学历:本科及以上
    专业:计算机等相关专业
    1.熟练使用oracle、mysql等主流数据库
    2.有数据接入工作经验
    3.最好有国网数据目录建设经验

    简历投递方式:
    "1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和317854561@qq.com
    2.邮件命名方式:姓名-投递公司-投递岗位"

    招聘人数:2人;持阿里云认证优先
    ]]>
    【华云中盛科技股份有限公司】- 招聘职位详情(社招) Fri, 20 Jun 2025 02:20:33 +0800 华云中盛科技股份有限公司介绍

    华云中盛科技股份有限公司成立于1998年,是国内领先的智能创新解决方案服务商,国家高新技术企业。公司总部位于深圳市南山区高新科技园,在北京、上海、广州、南京、杭州等全国20多个省市及河内、埃塞、雅加达、孟买等多个海外城市设有分支机构和办事处。

    公司长期致力于新型数据库、大数据、人工智能产品和工具的研发,通过技术沉淀和产品创新,研发出了分布式数据库、数据资产管理平台、统一应用开发中台、HAI人工智能开放平台、AI风险控制平台等核心产品,并以此为基础,结合多年行业经验,为税务、政务、检务、金融等各行业提供了丰富的智能化行业解决方案,助力客户持续创新,有效提升客户数据的利用价值和智能化管理水平,帮助用户实现业务系统从数字化到智能化的转型。

    公司现有员工一千多人,拥有专利数十项,著作权近百项。

    随着数字经济的高速发展,公司携手阿里云、华为云、腾讯云等主流云厂商进行深度合作,为行业客户提供云计算、大数据全栈式服务,打通最后一公里。为国家实施大数据战略,加快数字中国建设,推动数字经济做大做强贡献力量。

    未来,将继续深耕云服务、大数据+人工智能+行业领域,不断丰富和优化行业服务解决方案,以“助力客户持续创新,让智能技术普惠国人”为使命,挖据数据价值,成长为行业中具有深远影响力的高科技企业。

    职位1:迁移上云咨询工程师

    Base地点: 北京/武汉
    岗位职责:
    1、基于阿里云产品和解决方案的能力,协助企业客户实现IT系统架构转型和云化,提供上云迁移方案设计、云上架构规划、迁云咨询、与实施服务;
    2、客户上云以后,基于阿里云产品的能力,为客户设计应用系统在云上同城异地高可用架构、两地三中心容灾架构方案、以及云上监控运维、云上架构最佳实践方案等。
    3、为客户提供云上DevOPS体系建设方案的咨询和实施服务,优化业务交付效率,加速企业的数字化转型
    具体要求:
    1、计算机、电子类相关专业毕业,电子,自动化,计算机,通信等相关专业专业背景,五年以上工作经验。
    2、精通Linux、windows系统,对于常用的运维工具,开源软件(Nginx、Tomcat、Hadoop、Hbase、zookeeper、MQ、Redis等)的部署安装及配置优化经验。
    3、熟悉微服务、容器化架构,以及相关技术,对Istio、Docker、Kubernates,mesos等容器编排技术有丰富的实践和落地经验。
    4、精通MySQL、SQL Server、Oracle、PostgreSQL、Redis、Memcache等两种以上主流数据库和缓存技术,有丰富的数据库的优化维护经验。
    5、熟悉阿里云产品,通过阿里云ACP、Oracle OCP、PMP等相关认证优先。
    6、有大型综合项目的交付和上云经验。
    7、有较好的文档书写能力。
    其他要求:
    1、有阿里云项目经验优先
    2、善于沟通,具有团队意识
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:1人;持阿里云认证优先

    职位2: 大数据运维工程师

    Base地点: 北京
    岗位职责:
    1、独立完成云平台或数据中台软硬件环境搭建;
    2、独立完成应用上云包含应用评估、指导改造、配合上云实施、添加监控等;
    3、在指导下完成应用安全加固、服务器各类软件版本升级;
    3、及时处理各类异常报警、保障平台的运行稳定;
    4、及时响应用户咨询,为用户提供技术支持,架构优化建议等;
    5、深入研究云平台各类组件,提出优化意见,推进平台完善。
    具体要求:
    1.良好的语言表达和沟通能力,较强的服务意识与团队合作精神
    2.有主动推进预定计划/方案实施的意识,愿意主动学习
    3.熟悉阿里云弹性计算或大数据产品(包括ecs、odps、rds、drds、SLB、VPC、ads等常规组件使用),熟悉阿里云OpenApi;
    4.深入理解Linux操作系统,能够对主机性能进行分析和优化;
    5.熟悉常用的监控软件nagios、zabbix、cAdvisor、Prometheus、openfalcon中的一种或多种及Ansible自动化工具;
    6.熟悉常用的虚拟化技术,了解docker常见的使用方法,编排方式;
    7.熟悉常用服务的安装配置,例如:Nginx、JDK、DHCP、DNS等;熟悉网络路由配置;
    8.熟练使用shell、python、go中的一种或多种;
    9.具备2年以上阿里云规划、实施、运维和管理经验;具备阿里云ACP认证;有大规模服务器自动化安装、自动化运维经验的优先
    其他要求:
    1.熟悉阿里云大数据组件优先
    2.善于沟通,具有团队意识
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:2人;持阿里云认证优先

    职位3:云平台运维工程师

    Base地点: 福州/武汉
    岗位职责:
    1、负责基于阿里专有云云平台建设、系统规划、日常运营等工作;
    2、负责云平台资源的分配、搭建、部署、监控、调优、升级、日常维护工作;
    3、建立和完善云平台安全的监控体系和事件处理机制,确保业务的稳定运行;
    4、解决云平台运行过程中技术问题,对疑难问题进行分析并解决;
    5、制定云平台的运维流程、制度和规范;参与运维工作相关流程标准设计、编写。
    具体要求:
    1、 计算机、电子类相关专业毕业,电子,自动化,计算机,通信等相关专业专业背景,五年以上工作经验。
    2、熟悉阿里云平台架构及相关组件特性;
    3、熟悉主流的私有云产品(阿里云、华为云),掌握KVM、Xen、VMware等虚拟化技术、SDN虚拟网络、分布式云存储、传统SAN存储等相关技术;
    4、熟悉Linux操作系统、主流的存储产品、主流数据库的日常运维,掌握网络技术和信息安全知识;
    5、具有系统集成、业务迁云等相关工作经验,参与过私有云建设项目者优先;
    6、具备阿里云计算相关认证证书和相应从业经验优先。
    7、做事积极主动,有强烈的责任心,善于学习,有较强的团队沟通协调能力;
    其他要求:
    1.有阿里云项目经验优先
    2.善于沟通,具有团队意识
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:1人;持阿里云认证优先
    ]]>
    基因行业容器存储解决方案 Fri, 20 Jun 2025 02:20:33 +0800 1、基因行业背景

    1.1. 中国基因市场分析

    基因组学是未来精准医疗的“基石”。70%的基因企业选择使用云计算来处理基因组相关业务。过去一年内,阿里云为基因医学影像数据提供了安全可靠的存储,500%数据增长,增长迅猛。强大无限制的计算能力,阿里云支撑了百万以上的基因样本数据分析工作。
    1.png

    2.  基因行业的存储痛点和需求分析

    2.1. 基因组学数据管理相对复杂

    一家大型基因测序功能公司每日会产生 10TB 到 100TB 的下机数据,大数据生信分析平台需要达到 PB 级别的数据处理能力,这背后需要生物科技和计算机科技的双向支撑。测序应用从科研逐步走向临床应用,计算模式从离线向在线演进,交付效率越来越重要。多变的数据处理流程,以及大量的生物信息软件版本的管理,大量排队的数据处理任务,由于生物信息软件的运行特性CPU平均利用率仍然不足,难以无人职守的处理失败任务,成本居高不下等等问题存在于以下的现有的系统中,催生了生物信息行业对云计算和云原生技术的拥抱姿态,在成本,弹性,效率,规模也提出了更高要求。

    从基因业务角度理解,基因行业的数据处理的主要需求是提升效率,降低成本。基因场景的数据传输要求高速、稳定。如下图所示,是基因业务的数据处理流程图。
    2.png

    典型的基因行业数据传输与交付场景如下:
    •        测序服务商海量数据上云,数据上云有多重途径,通过OSS工具、存储网关、闪电立方等。一般1PB数据离线迁移,最快1周上云。
    •        科技服务公司:企业客户数据交付。数据交付,通知杭州到上海,同账号的跨地域复制的数据交付,在线迁移服务,完成跨地域、跨账号交付。杭州传到全球访问,全球访问加速。杭州传到企业用户,基于阿里云ram子账号,企业客户自己账号系统的交付体系。
    •        样本数据保存进行高吞吐分析计算,对存储要求高吞吐、高IOPS,对计算资源要求弹性、横向扩展。
    •        科研院所分析结果实现全球数据分发快速,一般每天交付19TB数据,全球14个数据中心保障快速就近交付。
    •        分析结果长期保存归档,要求高性价比、高可靠性。

    2.2. 典型的基因行业的数据存储痛点
    1、数据存储:数据增长快,存储费用高,管理困难;长期保存数据可靠性难以保障;需要寻求低成本大数据量的数据压缩方式;元数据管理混乱,数据清理困难。

    2、分发共享:海量数据需要快速、安全的分发到国内多地及海外;传统硬盘寄送方式周期长,可靠性低;多地中心数据需要共享访问。
    3、计算分析:批量样本处理时间长,资源需求峰谷明显,难以规划;大规模样本的数据挖掘需要海量计算资源,本地集群难以满足;计算工作流流程迁移困难、线上线下调度困难、跨地域管理困难;线下弹性能力差,按需计算需求。

    3、阿里云基因容器存储解决方案

    阿里云上的基因行业的容器存储解决方案架构如下:
    3.png
     
    阿里云基因文件存储解决方案说明:
    •        基于AGS/ACK工作流搭建极速、低成本、高精度的基因测序的容器计算环境,根据业务需求,按需弹性提供计算资源,解决业务高峰期排队问题的同时,提高资源利用率。
    •        文件存储可以对接容器基因计算环境,满足基因测序的计算和数据共享需求:
    •       NAS为基因行业用户提供低时延、高 IOPS 的文件存储,用于基因数据分析中的共享存储,保存下机数据和组装后的数据,以及过程中的中间数据。
    •        NAS计算结果数据会同步到 OSS 存储,文件存储集成OSS 提供海量存储空间,同时通过冷数据归档存储,降低存储成本。保存测序仪下机数据和组装后数据以及分析结果数据,用于数据的分发、归档、交付。保证10000+用户同时上传、下载数据,提高数据交付效率。

    阿里云整体解决方案在基因行业的优势:
    •       生态闭环:70%的基因行业客户都在阿里云上,包括100%的测序工厂头部客户跟阿里云有高速专线,基因数据在阿里云上各个基因客户之间高速流动;
    •       产品成熟:K8S、ACK、AGS、OSS、NAS等产品都已经商业化多年,在基因行业客户得到广泛使用。对比友商,我们的产品稳定,功能丰富,性价比高;
    •       行业老兵:服务基因行业多年,深入理解客户的业务场景和痛点,针对性的解决了数据上云、数据分析、数据交付的问题,给客户提供端到端的解决方案;
    •       全球化:借助阿里云全球基础设施以及跨境高速通道,帮助客户拓展海外业务。

    阿里云文件存储解决方案在基因行业的优势:
    •       扩展能力强:集群最大规模支持近万个节点,容量可达数十PB规模;便于基因海量数据的性能升级和容量扩展。
    •       性能能力强:协助客户使用高性能文件存储;有力支撑基因业务增长带来的大容量和高性能需求;大大提高海量数据快速分发和基因计算分析效率。
    •       更经济:通过阿里云文件存储服务构建基因测序高性能工作负载;没有一次性成本投入或冗长的采购周期,享受按需付费、更经济的基础设施资源。
    •       灵活敏捷:阿里云高性能文件存储服务,与ACK等计算集群整合,支持集群直接挂载文件系统;多种产品选型:通用型NAS、CPFS公共云及CPFS一体机,满足不同基因用户性能、成本、多云部署需求;灵活售卖方式,资源弹性伸缩,按需购买;支持云下和云上整体解决架构,既提供线下固定资产输出方案,满足对数据存储管理和基因业务高性能的双重要求,也提供快速数据上云的方案。
    •       数据生命周期管理:支持数据智能流转至低频节约成本;支持集成OSS,实现数据计算、分发、归档、交付的智能数据流转;智能数据冷热分层,同时满足基因快速高效计算和海量基因数据成本优化需求。阿里云文件存储解决方案用户收益:
    •       敏捷,按需使用,弹性扩展
    •       超高性能存储提高基因计算的高性能和效率
    •       数据冷热分层,降低成本

    4、基因行业容器文件存储介绍

    阿里云文件存储NAS为非结构化数据提供存储服务。随着云原生的快速发展,许多公司的容器化应用程序使用NAS存储数据,有些甚至存储PB级数据。阿里云在云原生布局上提供容器K8s服务(ACK)、弹性容器实例(ECI)、容器服务ACK/ASK,容器实例使用NAS文件系统作为持久化存储。容器存储离不开共享文件存储。通常,彼此共享数据的容器自然需要共享文件存储,长期运行的容器也可使用共享文件存储应对故障。阿里云文件存储同时满足容器应用场景对持久化存储弹性伸缩、灵活挂载、高性能的需求。此外,容器镜像的配置文件或初始加载数据存储可以在 NAS 文件存储上实现共享,在容器批量加载时实时读取,多 POD 间通过 NAS 共享持久化数据,在 POD 故障时可以进行故障切换。阿里云NAS是容器存储的自然选择,因为它是一种完全托管的服务,配置简单,添加或删除数据时自动扩展,和容器基础架构一样灵活便捷。它还可以扩展到每秒数GB的吞吐量以及每秒数万的IOPS。它基于 POSIX 文件接口,提供 NFS / SMB 协议,天然适配原生操作系统,提供共享访问,同时保证数据一致性和锁互斥。文件存储中的数据在可用区内以多副本冗余方式存储,避免数据的单点故障风险。

    文件存储目前包括三款产品,分别是通用型NAS、极速型NAS和CPFS并行文件存储,为企业应用、大数据处理、小文件存储、高性能计算等场景提供全方位解决方案。
    了解更多阿里云文件存储

    阿里云文件存储主要具备以下特性:
    无缝对接容器存储插件,简化的云原生体验:文件存储基于POSIX/NFS/SMB文件接口,现有应用无需任何改造,可直接从本地磁盘平滑迁移至文件存储。针对容器编排,文件存储提供了CSI、Flexvolume存储插件,简化部署和使用,拥有完整的云原生体验。
    共享持久化数据,故障灵活切换:文件存储具备多节点共享访问能力,文件存储还可以在不同可用区(不支持跨地域)、不同VPC之间共享。多Pod间通过NAS共享持久化数据,在POD故障时可以进行故障切换。统一的名字空间,统一的管理方式。弹性文件系统无需扩容缩容,0-10PB完全按量。
    高性能加速,在容器批量加载时实时读取:文件存储基于分布式存储架构,具备性能水平扩展能力。提供不同类型、不同规格存储产品,充分满足各类应用的吞吐、延迟、IOPS和小文件等性能需求。文件存储支持us时延,数十GB吞吐,加速存储访问,提升应用性能,在容器批量加载时实时读取数据,支撑有状态应用、AI数据智能应用。
    企业级应用特性,Pod存储安全可靠:文件存储支持副本、纠删码技术,同时通过备份、快照提供完整的数据保护和恢复机制。支持传输、落盘加密,确保数据不会被任何人窃取和篡改。统一命名空间支持海量pod存储隔离和统一管理。
    持久化数据生命周期管理,优化成本:文件存储提供按量付费模式,您只需为正在使用的存储空间付费,不需要提前配置存储,并且不存在最低费用或设置费用。借助文件存储的共享能力可以节约大量的冗余数据拷贝和同步费用。数据生命周期管理,支持冷热分层,自动归档冷数据,降低成本90%。

    了解更多阿里云文件存储

    ]]>
    【四川捷云信通信息技术有限公司】- 招聘职位详情(校招) Fri, 20 Jun 2025 02:20:33 +0800 四川捷云信通信息技术有限公司

    四川捷云信通信息技术有限公司(以下简称:捷云信通)是一家由留学归国的资深技术人员创办的以大数据、云计算、物联网技术为核心的高新技术企业。捷云信通现有员工100余人,其中技术研发人员占比60%,公司90%的员工拥有大学本科以上学历,硕士研究生2人,博士专家7人。目前公司已拥有专利12项、软件著作权20项。捷云信通依托“物联网+大数据+云计算”技术,为气象、环保、交通、农业等诸多领域提供稳定可靠的数据支撑、精细化管理和整体解决方案。

    捷云信通除坚持自主创新和研发外,也是阿里云核心授权服务中心。2018年产值突破2亿,已连续三年保持百分之三十的年增长,顺利挺近中国一流云计算服务商行列。自2015年以来公司已承担完成了重庆跨境电商平台项目、成都英特龙O2O商城、重庆181美食城餐饮服务系统、美国PPI公司的医疗卫生监控平台,华为图并行计算软件,ALP测井数据集成平台等项目,以优良的专业素质和用户至上的精神获得客户好评。同时公司着力于Saas产品的研发运营,能为客户提供更为全面的的大数据服务和云计算平台服务。

    捷云信通核心团队成员由物联网、人工智能、云计算等方面有资深研究的权威专家和软硬件、算法、大数据等方面具备多年丰富从业经验的工作者组成。公司合伙人在信息化领域从业多年,带领团队成功完成过多个研发项目,具有极丰富的行业经验。

    职位1: 云计算客户经理

    Base地点:
    岗位职责:
    1、寻找潜在合作伙伴。
    2、与潜在合作伙伴进行商务洽谈。
    3、行业渠道建设,渠道管理和渠道关系维护等工作。
    4、开拓建立市场的代理渠道。
    5、市场状况竞争品牌的信息进行调研反馈。
    6、制定市场推广计划及方案。
    具体要求:
    工作年限:1年以上
    学历: 大专以上
    专业:计算机及相关专业
    1、具有计算机及相关专业专科及以上学历,有安全或云计算销售经验1年及以上优先考虑;
    2、熟悉互联网行业,能够独立开发合作伙伴,完成销售任务,有金融、电商、教育、互联网客户资源者优先;
    3、具有独立开发客户的能力,具备较强的学习能力、人际沟通能力和解决问题能力;
    4、为人坦诚自信,乐观进取,高度的工作热情,有良好的团队合作精神;
    简历投递方式:
    "1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位"

    招聘人数:若干人;持阿里云认证优先
    ]]>
    【广东唯一网络科技有限公司】- 招聘职位详情(校招) Fri, 20 Jun 2025 02:20:33 +0800 广东唯一网络科技有限公司介绍

    广东唯一网络科技有限公司(简称“唯一网络”)正式成立于2006年,是国内知名的云服务综合解决方案提供商。2018年4月通过中国证券监督管理委员会上市公司并购重组审核委员会审核,正式成为南兴装备股份有限公司(股票代码:002757)的全资子公司。

    唯一网络总部位于东莞,并设立了深圳、厦门、北京、广州、上海五大分公司。目前唯一网络及旗下子公司共拥有软件著作权达90多项,申请发明专利7项,其“唯云公有云服务平台”和“唯一网络安全防护技术服务”产品均被认定为2019年广东省高新技术产品,并已通过信息系统安全等级保护备案证明(第三级)、ISO9001质量管理体系认证、ISO27001信息安全管理体系认证、ITSS运维能力成熟度认证,两化融合管理体系认证,先后获得了“国家高新技术企业”、“广东省守合同重信用企业”、”广东省创新发展示范企业“、“东莞市十大倍增标杆企业”等荣誉称号。

    职位1: 业务助理

    Base地点: 厦门
    岗位职责:
    1、处理客户日常业务咨询,收集客户问题,快速响应;
    2、跟进与客户的需求落地、合同签署、回款;
    3、业务数据、销售提成统计,协助分析;
    具体要求:
    工作年限:一年以上
    学历: 本科以上
    专业:管理类或统计类专业
    1、本科或以上学历,管理类或统计类专业优先;
    2、有大型企业工作经验优先,一年以上助理类或运营类工作经验;
    3、熟练办公软件操作及公式的应用,擅长EXCLE,WORD,PPT,必须具备数据分析及书面表达能力;
    4、善于沟通,具备良好逻辑思维能力和沟通协调能力;
    5、抗压性强,做事有自己的思考,懂得主动反馈并提供多种解决方案,执行能力强。

    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和zengl@wy.cn
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:1人;持阿里云认证优先

    职位2: 客服专员

    Base地点:厦门
    岗位职责:
    1、熟悉公司主要产品,负责公司产品的推广和销售;
    2、通过电话或网络寻找客户,开发新客户,拓展与老客户的业务,建立和维护客户档案;
    3、执行公司制定的销售政策和销售业绩目标;

    具体要求:
    工作年限:一年以上
    学历: 大专及以上
    专业:不限
    1、有IT销售工作经验者优先(对域名、服务器、云主机、系统集成或硬件等互联网产品熟悉者优先);
    2、较熟悉计算机和互联网,坚持建立长期、诚信的客户关系,良好的人际沟通能力和表达能力,良好的客户服务意识;
    3、表达能力强,逻辑思维清晰,具备较强的执行力和沟通表达能力。
    其他要求:
    有IT销售工作经验者优先(对域名、服务器、云主机、系统集成或硬件等互联网产品熟悉者优先)
    简历投递方式:
    1、简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和zengl@wy.cn
    2、邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:2人;持阿里云认证优先

    职位3: 政务专员

    Base地点:厦门
    岗位职责:
    1、关注、研究各级政府部门的政策文件,定期给出观察报告与建议;
    2、协助政府政策申办中的了解文件要求,准备、制作符合政府要求的申报文件;
    3、助公司相关经营资质的申请工作;
    4、完成部门相关项目的统计与跟进工作;
    5、领导交予的其他工作。

    具体要求:
    工作年限:不限
    学历: 本科以上
    专业:不限
    1、本科以上;
    2、熟练使用office办公软件;
    3、熟练掌握行政管理、商务写作、公共关系等方面知识;
    4、文档阅读与撰写能力强,擅于沟通;
    5、优秀应届生亦可考虑。

    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和zengl@wy.cn
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:1人;持阿里云认证优先

    职位4: 机房运维工程师

    Base地点:厦门
    岗位职责:
    1、负责公司客户服务器日常维护;
    2、负责服务器故障的处理,网络不通,无法远程 等等故障;
    3、负责其他运维事项;
    具体要求:
    工作年限:不限
    学历: 不限
    专业:计算机等相关专业
    1、熟悉windows服务器操作系统并能熟练运用 windows2003/2008 ;
    2、了解Linux和UNIX系统;
    3、熟悉常用服务器软件的操作与使用;
    4、熟悉网络知识,处理常见网络故障;
    5、有责任心,服从管理,做事细心,有团队合作精神,能承受工作压力;
    6、具备良好的服务意识,头脑清晰,思维敏捷,能接受7*24小时轮班制;
    7、欢迎优秀的应届生加入。

    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和zengl@wy.cn
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:2人;持阿里云认证优先

    职位5: 运维工程师

    Base地点:东莞
    岗位职责:
    1、负责公司的服务器设备安装,系统部署和日常的运维工作;
    2、根据要求安装和配置操作系统.
    3、根据要求进行系统故障的排查和处理
    4、安装和维护公司计算机、服务器系统软件和应用软件;
    5、解决排除各种软硬件故障,做好记录;
    具体要求:
    工作年限:应届生
    学历: 大专及以上
    专业:计算机相关专业

    1、了解常见计算机的硬件、能排除简单硬件故障。
    2、了解计算基础网络和具备计算机基础知识.
    3、熟悉Windows、Linux系列服务器系统安装及操作,可以进行简单系统配置和故障分析和系统维护,熟悉系统常用的命令和配置步骤。
    4、熟悉常见的网络服务,FTP、WEB、Email等服务原理;

    其他要求:
    1、有责任心,服从安排,做事细心,有团队合作精神,能承受工作压力;
    2、沟通良好,计算机相关专业的应届毕业生或者有IDC行业工作经验的。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和zengl@wy.cn
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:1人;持阿里云认证优先
    ]]>
    【毕马威(中国)】- 招聘职位详情(社招) Fri, 20 Jun 2025 02:20:33 +0800 毕马威(中国)介绍

    毕马威中国在二十四个城市设有二十六家办事机构,合伙人及员工约12,000名,分布在北京、长沙、成都、重庆、佛山、福州、广州、海口、杭州、济南、南京、宁波、青岛、上海、沈阳、深圳、苏州、天津、武汉、厦门、西安、郑州、香港特别行政区和澳门特别行政区。在这些办事机构紧密合作下,毕马威中国能够高效和迅速地调动各方面的资源,为客户提供高质量的服务。

    毕马威是一个由专业服务成员所组成的全球网络。成员所遍布全球147个国家和地区,拥有专业人员超过219,000名,提供审计、税务和咨询等专业服务。毕马威独立成员所网络中的成员与瑞士实体 — 毕马威国际合作组织(“毕马威国际”)相关联。毕马威各成员所在法律上均属独立及分设的法人。

    职位1:Senior Consultant, Cyber Security

    Base地点:北京
    岗位职责:
    ·        Comprehensive quantitative and qualitative security risk assessments.
    ·        Development & implementation of security risk mitigation & compliance plans.
    ·        Evaluation & development of information security policies, standards and procedures.
    ·        Design and implementation of Information Security Frameworks based on industry good practices and/or regulatory frameworks.
    ·        Compliance assessments against industry or regulatory frameworks such as, ISO27001, PCI DSS, and local specific regulations.
    ·        Design, development and deployment of data classification structures.
    ·        Execution of business impact assessments delivering Business Continuity (BC) and Disaster Recovery (DR) Plans.
    ·        Development and delivery of security awareness trainings.
    具体要求:
    工作年限:三年以上
    学历: 在校生
    专业:计算机等相关专业
    ·        Strong knowledge of enterprise technologies, especially networking principles and internet-based technologies, with self-motivated learning ability.
    其他要求:
    ·        Hands-on experience conducting network scanning and penetration testing preferred.
    ·        Experience with security testing tools is an advantage.
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    招聘人数:2人;持阿里云认证优先
    ]]>
    必须掌握的分布式文件存储系统—HDFS Fri, 20 Jun 2025 02:20:33 +0800 HDFS(Hadoop Distributed File System)分布式文件存储系统,主要为各类分布式计算框架如Spark、MapReduce等提供海量数据存储服务,同时HBase、Hive底层存储也依赖于HDFS。HDFS提供一个统一的抽象目录树,客户端可通过路径来访问文件,如hdfs://namenode:port/dir-a/a.data。HDFS集群分为两大角色:Namenode、Datanode(非HA模式会存在Secondary Namenode)

    Namenode

    Namenode是HDFS集群主节点,负责管理整个文件系统的元数据,所有的读写请求都要经过Namenode。

    元数据管理

    Namenode对元数据的管理采用了三种形式:

    1) 内存元数据:基于内存存储元数据,元数据比较完整

    2) fsimage文件:磁盘元数据镜像文件,在NameNode工作目录中,它不包含block所在的Datanode 信息

    3) edits文件:数据操作日志文件,用于衔接内存元数据和fsimage之间的操作日志,可通过日志运算出元数据
    fsimage + edits = 内存元数据

    注意:当客户端对hdfs中的文件进行新增或修改时,操作记录首先被记入edit日志文件,当客户端操作成功后,相应的元数据会更新到内存元数据中

    可以通过hdfs的一个工具来查看edits中的信息
    bin/hdfs  oev  -i  edits  -o  edits.xml
    查看fsimage
    bin/hdfs  oiv  -i  fsimage_0000000000000000087  -p  XML  -o  fsimage.xml

    元数据的checkpoint(非HA模式)

    Secondary Namenode每隔一段时间会检查Namenode上的fsimage和edits文件是否需要合并,如触发设置的条件就开始下载最新的fsimage和所有的edits文件到本地,并加载到内存中进行合并,然后将合并之后获得的新的fsimage上传到Namenode。checkpoint操作的触发条件主要配置参数:

    dfs.namenode.checkpoint.check.period=60  #检查触发条件是否满足的频率,单位秒
    dfs.namenode.checkpoint.dir=file://${hadoop.tmp.dir}/dfs/namesecondary
    dfs.namenode.checkpoint.edits.dir=${dfs.namenode.checkpoint.dir}
    #以上两个参数做checkpoint操作时,secondary namenode的本地工作目录,主要处理fsimage和edits文件的
    dfs.namenode.checkpoint.max-retries=3  #最大重试次数
    dfs.namenode.checkpoint.period=3600  #两次checkpoint之间的时间间隔3600秒
    dfs.namenode.checkpoint.txns=1000000  #两次checkpoint之间最大的操作记录

    checkpoint作用

    1.加快Namenode启动

    Namenode启动时,会合并磁盘上的fsimage文件和edits文件,得到完整的元数据信息,但如果fsimage和edits文件非常大,这个合并过程就会非常慢,导致HDFS长时间处于安全模式中而无法正常提供服务。SecondaryNamenode的checkpoint机制可以缓解这一问题

    2.数据恢复
    Namenode和SecondaryNamenode的工作目录存储结构完全相同,当Namenode故障退出需要重新恢复时,可以从SecondaryNamenode的工作目录中将fsimage拷贝到Namenode的工作目录,以恢复Namenode的元数据。但是SecondaryNamenode最后一次合并之后的更新操作的元数据将会丢失,最好Namenode元数据的文件夹放在多个磁盘上面进行冗余,降低数据丢失的可能性。

    注意事项:

    1.SecondaryNamenode只有在第一次进行元数据合并时需要从Namenode下载fsimage到本地。SecondaryNamenode在第一次元数据合并完成并上传到Namenode后,所持有的fsimage已是最新的fsimage,无需再从Namenode处获取,而只需要获取edits文件即可。

    2.SecondaryNamenode从Namenode上将要合并的edits和fsimage拷贝到自己当前服务器上,然后将fsimage和edits反序列化到SecondaryNamenode的内存中,进行计算合并。因此一般需要把Namenode和SecondaryNamenode分别部署到不同的机器上面,且SecondaryNamenode服务器配置要求一般不低于Namenode。

    3.SecondaryNamenode不是充当Namenode的“备服务器”,它的主要作用是进行元数据的checkpoint

    Datanode

    Datanode作为HDFS集群从节点,负责存储管理用户的文件块数据,并定期向Namenode汇报自身所持有的block信息(这点很重要,因为,当集群中发生某些block副本失效时,集群如何恢复block初始副本数量的问题)。

    关于Datanode两个重要的参数:

    1.通过心跳信息上报参数

    <property>
    <name>dfs.blockreport.intervalMsec</name>
    <value>3600000</value>
    <description>Determines block reporting interval in milliseconds.</description>
    </property>

    2.Datanode掉线判断时限参数

    Datanode进程死亡或者网络故障造成Datanode无法与Namenode通信时,Namenode不会立即把该Datanode判定为死亡,要经过一段时间,这段时间称作超时时长。HDFS默认的超时时长为10分钟30秒。如果定义超时时间为timeout,则超时时长的计算公式为:

    timeout = 2 * heartbeat.recheck.interval(默认5分钟) + 10 * dfs.heartbeat.interval(默认3秒)。
    <property>
            <name>heartbeat.recheck.interval</name>
            # 单位毫秒
            <value>2000</value>
    </property>
    <property>
            <name>dfs.heartbeat.interval</name>
            # 单位秒
            <value>1</value>
    </property>

    HDFS读写数据流程

    了解了Namenode和Datanode的作用后,就很容易理解HDFS读写数据流程,这个也是面试中经常问的问题。

    HDFS写数据流程

    1.jpg

    注意:
    1.文件block块切分和上传是在客户端进行的操作

    2.Datanode之间本身是建立了一个RPC通信建立pipeline

    3.客户端先从磁盘读取数据放到一个本地内存缓存,开始往Datanode1上传第一个block,以packet为单位,Datanode1收到一个packet就会传给Datanode2,Datanode2传给Datanode3;Datanode1每传一个packet会放入一个应答队列等待应答

    4.当一个block传输完成之后,客户端会通知Namenode存储块完毕,Namenode将元数据同步到内存中

    5.Datanode之间pipeline传输文件时,一般按照就近可用原则
    a) 首先就近挑选一台机器
    b) 优先选择另一个机架上的Datanode
    c) 在本机架上再随机挑选一台

    HDFS读数据流程

    2.jpg

    注意:
    1.Datanode发送数据,是从磁盘里面读取数据放入流,以packet为单位来做校验

    2.客户端以packet为单位接收,先在本地缓存,然后写入目标文件

    客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件

    HDFS HA机制

    HA:高可用,通过双Namenode消除单点故障。

    3.jpg

    双Namenode协调工作的要点:

    1.元数据管理方式需要改变
    a) 内存中各自保存一份元数据

    b) edits日志只能有一份,只有active状态的Namenode节点可以做写操作

    c) 两个Namenode都可以读取edits

    d) 共享的edits放在一个共享存储中管理(qjournal和NFS两个主流实现,图中以放在一个共享存储中管理(qjournal和为例)

    2.需要一个状态管理功能模块
    a) 实现了一个zk failover,常驻在每一个Namenode所在的节点

    b) 每一个zk failover负责监控自己所在Namenode节点,利用zk进行状态标识,当需要进行状态切换时,由zk failover来负责切换,切换时需要防止brain split现象的发生

    ]]>
    开放、普惠、高性能-SLS时序存储助力打造企业级全方位监控方案 Fri, 20 Jun 2025 02:20:33 +0800

    无所不在的时序数据

    时间带走一切,长年累月会把你的名字、外貌、性格、命运都改变。 ---柏拉图


    随着时间的推移,万事万物都在不停的变化,而我们也会用各种数字去衡量这些变化信息,比如年龄、重量、速度、温度、金钱...在数字化时代中,我们会把这些随着时间变化的数据保存起来,挖掘这些数据的价值。通常我们会称这类数据为---时序数据。

    时序数据用于描述物体在时间维度上的状态变化信息。

    image.png

    时序数据在各行各业都得到了非常广泛的应用,例如股票走势、交易趋势、服务器指标、脉搏心跳、定位坐标、能耗趋势等等,而这些数据几乎在所有的场景中都得到了应用,例如:

    1. 各类炒股软件提供众多不同维度的股票K线图,为广大股民提供参考标准;
    2. Apple Watch通过监控佩戴者的心率信息,帮助人们提早发现严重的心脏疾病;
    3. 国家电网通过分析各个小区、住户的用电量曲线,来判断是否有偷电漏电情况;
    4. 电商类的公司会监控平台的下单、交易、退货、评价等关键流程的变化趋势,用来快速发现各类异常;
    5. 各个游戏平台通过分析每个用户角色的操作、位置等变化规律,来判断是否使用了作弊辅助工具...

    我们需要一个什么样的时序存储

    为了能够支撑各种场景的时序分析、监控等需求,近几年在开源和商业领域均出现了一些时序存储的引擎,例如TimescaleDB、CrateDB、InfluxDB、OpenTSDB、Prometheus等,这些存储引擎分别有自己的生态和适用场景,在某些场景下具有较高的优势,例如TimescaleDB基于PostgreSQL,如果是PG的用户可以快速上手;InfluxDB具有非常完善的生态,TICK(Telegraf、 InfluxDB、Chronograf、Kapacitor)可以快速上手;Prometheus对于云原生场景支持非常友好,PromQL也非常便捷灵活,已经成为Kubernetes上监控的实事标准。


    然而从实际公司的业务场景出发,对于时序数据会有更多的要求:

    1. 高性能:时序数据通常流量大、保存周期长且需要长时间的范围查询,能够支撑大规模的写入与快速查询是必备条件;
    2. 开放:通常公司内部会有多个部门对不同系统的时序数据进行不同类型的分析、监控等需求,因此时序存储需具备足够的开放能力,支持各种数据的接入以及下游消费;
    3. 低成本:这里的成本主要包括两方面:资源成本和人力运维成本。有摩尔定律的存在,单位资源成本越来越低,而单位人力成本每年都在提升,因此低成本的核心在于运维这套时序存储的人力成本;
    4. 智能化:尤其在海量监控对象的场景中,纯粹的静态规则很难发现某个监控对象的异常,因此时序存储上层需要附加智能化的算法,提升监控的准确率。

    SLS时序存储发布

    SLS的日志存储引擎在2016年对外发布,目前承接阿里内部以及众多企业的日志数据存储,每天有数十PB的日志类数据写入。其中有很大一部分属于时序类数据或者用来计算时序指标,为了让用户能够一站式完成整个DevOps生命周期的数据接入、清洗、加工、提取、存储、可视化、监控、问题分析等过程,我们专门推出了时序存储的功能,与日志存储一道为大家解决各类机器数据的存储问题。


    image.png

    时序存储整体架构如上图所示,接入层可以对接各类开源的采集软件以及SLS自己开发的高性能Logtail,同时支持各种语言SDK直接写入,也支持Kafka、Syslog等开放性协议;存储层是完全分布式架构,每个时序库可通过Sharding方式水平扩展,数据默认3副本高可靠存储;计算层与存储层分离,提供SQL、PromQL纯分析型语法,同时提供智能分析能力。基于SLS提供的采集、存储、分析等功能可快速构建企业自己的业务监控、微服务监控等方案。

    功能特点

    image.png

    SLS时序存储从设计之初就是为了解决阿里内部与众多头部企业客户的时序存储需求,并借助于阿里内部多年的技术积累,使之可以适应绝大部分企业级时序监控/分析诉求。SLS时序存储的特点主要有:

    1. 丰富上下游:数据接入上SLS支持众多采集方式,包括各类开源Agent以及阿里云内部的监控数据通道;同时存储的时序数据支持对接各类的流计算、离线计算引擎,数据完全开放;
    2. 高性能:SLS存储计算分离架构充分发挥集群能力,尤其在大量数据下端对端的速度提升显著;
    3. 免运维:SLS的时序存储完全是服务化,无需用户自己去运维实例,而且所有数据都是3副本高可靠存储,不用担心数据的可靠性问题;
    4. 开源友好:SLS的时序存储原生支持Prometheus的写入和查询,并支持SQL92的分析方法,可以原生对接Grafana等可视化方案;
    5. 智能:SLS提供了各种AIOps算法,例如多周期估算、预测、异常检测、时序分类等各类时序算法,可以基于这些算法快速构建适应于公司业务的智能报警、诊断平台。

    典型应用场景

    应用/业务监控

    image.png

    应用/业务监控是公司层面重要的工作之一,在阿里内部一直作为最重要的监控项在建设。通过SLS提供的各类数据采集功能将所有应用/业务数据统一、实时采集,利用数据加工把各个不同时期、不同风格的数据做结构化处理,基于结构化的数据就可以做一定的分析,但通常业务数据量级较大,我们还会使用SQL的聚合功能对数据进行一定的降维,使用降维后的聚合时序数据来做告警以及长期的监控指标回溯。

    云原生监控

    image.png

    随着云原生技术的普及,越来越多的公司开始技术转型到云原生架构,借助于Prometheus、OpenTelemetry等CNCF的开源Project可以快速采集到Kubernetes以及各类中间件、应用的监控信息,阿里云上的云监控获取到所有云产品的监控数据。利用SLS时序存储以及日志/Trace存储的能力,可以支持各类监控数据的统一存储,数据可无缝对接Grafana的可视化,在Grafana上构建基础设施、云产品、中间件、应用软件的全方位监控大盘。

    访问日志分析

    image.png

    访问日志作为网站、APP的入口流量记录,能够直接反映出当前应用是否正常,因此运维领域的必备监控项。通过Logtail采集原始的访问日志,可用来分析/调查每个用户的请求,也可用作归档/审计需求;但原始访问日志量较大,不太适合直接的监控,通常会通过预聚合的方式对数据进行降维,基于聚合后的时序数据进行实时监控,并可应用SLS提供的智能巡检功能对每个业务站点进行独立的智能监控。


    大家在使用SLS中遇到的任何问题,请加钉钉群,我们有专门的日志女仆24小时在线答疑,还有火锅哥和烧烤哥专业支持!~ SLS微信公众号定期会发布各类日志、监控领域的技术分享文章并定期举行抽奖,欢迎小伙伴们关注~


    另外欢迎对大数据、分布式、机器学习等有兴趣的同学加入,转岗、内推,来者不拒,请用简历狠狠的砸我,联系邮箱 davidzhang.zc@alibaba-inc.com !~
    image.png


    ]]>
    DataHub使用指南 Fri, 20 Jun 2025 02:20:33 +0800 快速入门教程

    1.开通DataHub

    ​ 使用DataHub的第一步,首先点击开通DataHub

    2.创建Project和 Topic

    • 具体创建方式参考文档:https://help.aliyun.com/document_detail/158785.html?spm=a2c4g.11186623.6.556.796958e1yVcaLO
    • 创建Topic方式解读,Tuple还是Blob?

      • Tuple支持的是强Schema的结构化数据,Blob指的是没有类型的非结构化数据,在实际中Blob就是只有一列为string类型的schema
      • 值得注意的是:使用Blob类型topic的话,数据会采用Base64加密,无论是抽样还是下游消费,都需要进行解密
      • Schema设计


    DataHub目前只支持字段的新增,不支持删除和修改,针对上游数据源字段经常发生变动的场景,建议设置允许字段为空,如果上游字段变更的话,针对多出来的字段可以通过SDK新增字段,而对于减少的字段则由于允许为空,值将会置为NULL,不会对业务造成影响
    
    • shard 和生命周期设置

      • shard在DataHub中代表的是并发通道,每个shard每秒吞吐限制为5M/s,每个shardQPS(每秒请求数)为2000次,您可根据这两项指标合理设置shard个数
      • 针对生命周期而言,可以根据业务需要设置,如果需要更改的话,可以使用Java SDK修改生命周期

    3.上游的选择

    DataHub目前支持的数据采集插件

    • OGG

      • OGG for MySQL
      • OGG for Oracle
    • LogStash
    • Flume
    • Canal插件
    • Fluentd

    https://help.aliyun.com/document_detail/158836.html?spm=a2c4g.11186623.6.588.5e65710b7RMCns
    通过SDK自定义写入DataHub

    注意:

    https://help.aliyun.com/document_detail/158841.html?spm=a2c4g.11186623.6.599.28c21333xe8wPo
    https://help.aliyun.com/document_detail/158834.html?spm=a2c4g.11186623.6.583.2db4710bEEOlFZ
    兼容Kafka

    https://help.aliyun.com/document_detail/168118.html?spm=a2c4g.11186623.6.586.6aec6bdbCi1ElZ
    DTS数据同步
    从PolarDB MySQL同步至Datahub
    从DRDS同步至DataHub
    DataHub目前的上游生态就是这样了

    4.指标查看 or数据抽样

    ​ 在将数据写入到DataHub之后,DataHub提供了可视化指标来查看内部情况,具体详情请查看

    指标查看metric详情最新.png

    ​ 用户如何查看数据质量,写入是否正确?可以通过Web抽样功能来查看数据

    5.订阅

    ​ 什么是订阅?

    • 订阅最主要的功能就是存储消费点位,以及通过点位重置重新消费
    • 用户可创建不同的订阅针对同一个Topic数据的不同消费模式
    • 创建同步自动会创建对应的订阅


    创建订阅,删除订阅请查看文档:https://help.aliyun.com/document_detail/158833.html?spm=a2c4g.11174283.6.584.78d763ef5KNv0Y

    6.同步数据到下游

    ​ 消费DataHub数据有两种方式,通过DataHub支持的同步数据库同步到下游,或者通过自定义SDK消费数据进行处理

    DataHub支持的同步类型:

    • Hologres
    • Maxcompute
    • ADS
    • ElasticSearch
    • 函数计算
    • OSS
    • TableStore
    • RDS/MySQL/ADS 3.0

    自定义SDK消费

    ​ 您可以使用SDK对DataHub数据进行消费

    ​ 同时DataHub协同消费解决多个消费者同时消费一个topic时,自动分配shard的问题,您也可以选择使用协同消费对DataHub数据进行处理

    ​ 同步往往是出现问题最多的,请参考  DataHub同步问题

    7.监控报警

    ​ 在同步数据过程中,DataHub支持了监控报警,目前只有订阅延迟报警这一项,您可以通过创建报警规则方式对DataHub同步到下游数据进行监控,当超过延迟时间阈值时,会通过钉钉、短信等多种方式提醒您。

    ​ 具体报警说明请查看文档:监控报警

    8 总结

    本文通过对DataHub的创建使用,上游数据源的选择,同步到DataHub的指标查看,以及下游类型的说明,阐述了DataHub做为数据通道的概念模型以及实际的落地场景,如有更多使用疑问,请加DataHub公共云群组进行反馈

    ]]>
    做好这个比周报更有用!【宜搭精品应用推荐】 Fri, 20 Jun 2025 02:20:33 +0800 11.jpg
    22.jpg

    OKR(Objectives and Key Results)即目标与关键成果法,是一套明确和跟踪目标及其完成情况的管理工具和方法。OKR可以有效确保员工紧密协作,把精力聚焦在能促进组织成长的、可衡量的贡献上。OKR管理系统是企业进行目标管理的一个简单有效的系统。和KPI重视考核不同,OKR更重视目标管理,并被大多数创造力驱动的新型科技公司采纳使用。

    受今年疫情影响,许多部门开始实行远程办公,在这种场景下,OKR系统可以更好地帮助团队聚焦目标、高效协作。然而,如何才能拥有团队自己的OKR系统呢?

    宜搭的精品应用模板之一“OKR管理系统”,囊括了OKR录入、目标里程碑录入、目标/KR变更、KR执行情况填写、KR进展更新等核心功能点,可以帮助团队同学对齐目标、聚焦重点、可视化目标里程。在宜搭模板市场,可一键复制使用,科学管理目标,快快喊上团队成员用起来吧~

    image.png

    • 应用名:OKR管理系统
    • 应用场景:团队OKR管理、项目协同管理
    • 用户:团队/项目成员、PM、管理者

    主要功能

    image.png

    01 OKR管理

    包括OKR录入、目标里程碑录入、目标/KR变更、KR执行情况填写、KR进展更新等功能。

    线上任务看板清晰明了,公开透明,可以帮助团队目标与个人目标保持一致,更好实现团队协作。

    image.png

    02 项目进度跟踪

    可通过报表、图表等形式实时展示个人的OKR情况、项目里程碑、项目进展等信息。

    定期反馈工作进展,填报当前进度、完成情况,以及需要协助支持的事宜,达到事半功倍的效果。

    image.png

    03 项目报表

    提供了从个人的视角、项目整体的视角、里程碑的完成度视角、任务的执行过程等视角的进度报表,直接展示个项目的完成情况,结果可量化,成员可以很直观地看到项目进展,互相激励,驱动团队和个人不断向目标前进。

    image.png

    应用价值

    1、提升项目协同效率:
    通过对团队各个协同项目进行资源、进度的实时监控,能够进行资源利用率的优化,提升团队整体的协同效率。

    2、提升团队积极性:
    将目标和成果可视化,调动团队的积极性,帮助成员主动去发现问题、解决问题。

    3、提升团队公开透明:
    以往模式无法全面了解团队各个成员手上的项目,缺乏公开透明性,OKR系统很好地解决了这一问题,和传统KPI相比,更适合中后台、产品技术团队使用。

    4 、项目进度及时同步
    对于每一次项目会议、每一个的项目信息表更、任务信息变更都提供了详细的记录查询,保证目标的完成度。

    立即试用

    ]]> 混合云容灾服务介绍 Fri, 20 Jun 2025 02:20:33 +0800 什么是混合云容灾服务

    混合云容灾服务(Hybrid Disaster Recovery, 简称 HDR)是一个为您的数据中心提供企业级应用的本地备份与云上容灾一体化的服务。

    混合云容灾服务解决的核心问题

    混合云容灾解决的两个核心问题是:

    • 应用级容灾 - 业务持续性 (Business Continuity) 保障:在数据中心故障或长时间系统维护作业时,在云上快速恢复应用运行,缩短业务停机时间,极大减少损失。
    • 数据级容灾:在数据中心备份您的数据库、虚拟机、物理机整机,备份数据存储在本地并自动上云。可在自建数据中心发生重大灾害时保障数据安全,同时提供高效的本地和云上的双重恢复。

    此外,利用混合云容灾服务的服务器整机复制能力,您可以方便地将本地服务器迁移到阿里云 ECS,无需重构,您就可以完成应用轻松上云。

    混合云容灾服务支持的业务类型

    混合云容灾服务目前支持两种业务类型:

    • 连续复制型容灾(CDR):解决企业关键应用的高标准容灾方案,提供秒-分级的 RPO 和 RTO 容灾。
    • 混合云大数据容灾(公测中):解决 Hadoop 集群数据的实时容灾复制,跨集群大数据湖建设,Hadoop 备份的问题,实现大数据集群间的近 0 RPO 实时双向复制。

    产品优势

    阿里云拥有世界水平的基础设施,随用随取的海量弹性资源,简单易用的计算、存储、网络、数据库、大数据服务,是企业天然的灾备中心。利用混合云容灾服务搭建基于阿里云的异地容灾方案是企业业务连续性和数据安全保障的理想选择。

    总成本低廉

    • 无需自建灾备中心,免去机房运维、硬件采购等成本。
    • 云上主要消耗存储资源、计算资源需求极低。
    • 可针对不同的应用需求以及不同的网络带宽,配置不同的 RPO、RTO,从而节约成本。
    • 相对自建灾备中心的方案,可以节约高达 80% 的费用。

    简单易用

    • 云下部署简单、云上资源全自动管理、控制台集中管控。
    • 备份恢复演练和容灾恢复演练可随时进行,一键启动、快速清理。

    RPO/RTO分级

    企业需要对重要性级别不同的应用制定阶梯化的 RPO/RTO。企业的基础架构,尤其是网络情况会制约能达到的容灾指标。

    • 连续复制型容灾(CDR)基于磁盘级实时数据复制技术,可以提供秒级-分钟级的 RPO/RTO。
    • 混合云大数据容灾提供近 0 RPO 的大数据容灾,可以将 Hadoop 集群容灾至阿里云 OSS 或 EMR,在 Hadoop 集群间双向实时复制,构建大数据湖。

    应用级容灾和数据级容灾

    • 支持将 Windows、 Linux 应用服务器做高效的容灾复制和云上恢复,实现应用级容灾。
    • 您可以只针对关键应用的数据,包括 SQL Server、Oracle 数据库、VMWare 虚拟机等进行定时备份和备份上云,实现数据级容灾。

    应用场景

    混合云容灾服务可以广泛地应用于各种数据保护和业务持续性场景。

    关键应用的异地容灾

    在本地数据中心上运行的应用可能面临各种意外情况。例如,由于软硬件环境被破坏而无法在短时间内恢复应用,火灾、自然灾害等事件甚至可能导致整个数据中心的重建。这些情况会导致关键应用长时间不可使用,从而对您的业务造成较大损失。当自有 IDC 内的应用无法短时间恢复时,混合云容灾服务能够帮助您将应用在云上快速拉起。
    使用混合云容灾网关后,核心应用的服务器镜像、应用数据、文件等都被持续复制到阿里云上。如果自有 IDC 内应用出现难以恢复的故障时,您可以在阿里云上启动容灾恢复网关,快速在 ECS 上恢复应用服务器运行,使应用迅速重新上线,极大减少业务损失。平时,您还可以方便地进行容灾演练,确保真实故障发生时恢复流程顺畅,保证容灾计划的准确性。
    混合云容灾服务让您无需承担自建灾备中心的巨大投入,也无需担心传统容灾方案复杂的软硬件部署运维,极大减少了异地容灾的成本,提高容灾的有效性。

    整机云迁移

    传统的上云迁移一般需要应用在云镜像上重新安装配置,ECS虚拟机重新配置,甚至应用重构等步骤,这个过程往往比较漫长。尤其是一些第三方开发的应用,因为软件依赖多且不明确、配置复杂等情况,上云迁移操作较为困难。
    混合云容灾网关或者灾备一体机提供了整机备份上云并在云上恢复的方式,让您可以在 ECS 中非常方便地真实还原云下服务器环境,让上云迁移变得简单直观。
    关于混合云容灾服务更多内容,亦可扫描下方二维码了解详情。
    image.png

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。
    阿里云SRE技术学院(钉钉圈子)-lyl.png

    ]]>
    阿里云工具应用中心 Fri, 20 Jun 2025 02:20:33 +0800

    庄子曰:“鲦鱼出游从容,是鱼之乐也。”惠子曰:“子非鱼,安知鱼之乐?”庄子曰:“子非我,安知我不知鱼之乐?”惠子曰:“我非子,固不知子矣;子固非鱼也,子之不知鱼之乐,全矣!”庄子曰:“请循其本。子曰‘汝安知鱼乐’云者,既已知吾知之而问我。我知之濠上也。”
    ——《庄子与惠子游于濠梁》

    几天前,在帮客户寻找解决方案时无意间发现了阿里云的工具应用中心。

    按照阿里云帮助文档的说法:

    工具应用中心是阿里云搭建的,面向企业云管理的工具平台。
    用户可以在这里根据使用场景,方便地找到由阿里云或第三方合作伙伴提供的应用产品,完成订购直接在阿里云管理控制台使用。应用中心提供了资源管理、配置编排、自动化运维、诊断分析、成本费用、运维工具等场景类目,并且在持续拓展类目。阿里云和第三方合作伙伴针对各个类目场景的客户需求设计了各具特色的独立应用。
    官方应用由阿里云自行研发和维护,并由阿里云提供售后服务。
    第三方合作伙伴应用,由经过阿里云资质审核认证的合作伙伴企业研发,通过阿里云安全审核后发布在阿里云管理控制台上。第三方合作伙伴应用托管在阿里云,受到全面的数据安全管控

    目前据我所知,工具应用中心有两个入口:
    一个是在控制台的左上角,阿里云图标左边的按钮点下去后,在菜单栏的最下面。

    1.png

    另一个是在阿里云控制台的右下角。
    2.png

    到本文截稿时,工具应用中心有8款应用,其中3款来自阿里云,5款来自第三方合作伙伴。

    3款阿里云的应用包括:

    • 等保2.0 预检、可以持续监控云上资源配置的合规性,避免正式检测时被要反复要求整改,便于更顺利的通过评测。
    • 逻辑编排、低代码的集成阿里云API和第三方服务,可以自动完成诸如证书过期检测、为新加入的安全组增加默认拒绝策略、自动为新加入的RAM用户或角色添加权限等工作。
    • 运维编排、主要面向阿里云ECS进行批量、定时操作,可以执行的操作包括批量执行命令、开关机、更换镜像、调整带宽等。

    5款第三方的应用包括:

    • 某某CMDB、一款轻量级配置管理系统,用于准确的映射业务支撑架构,提高运维效率。
    • 某某主机管家、一款堡垒机应用,对比阿里云官方的堡垒机可以提供一些额外的增值功能。
    • 某某费用管理、一款成本控制软件,用于成本分摊及账单可视化,提升资源运营效率。
    • 某某云迁移、用于无代理方式的整体迁移,对比阿里云免费提供的迁移中心,这款应用在对VMWare虚拟化平台进行上云迁移时不需要在迁移虚拟机上安装代理程序。
    • 某某蓝图编排、一款类似于Terraform 的IoC(Infrastructure as Code)产品,对应的阿里云自有服务叫做ROS,相对于ROS,这款产品在易用性上有自己的特色,阿里云ROS团队的小伙伴会有些许的紧张吧。

    这几款应用虽然是来自于第三方,但在使用体感上和阿里云官方服务并没有什么差异,用户并不需要为这些应用生产和分配AK(接入密钥)和权限。应用在第一次使用时阿里云会弹出授权确认窗口,用户单击确认授权即可。另外按照阿里云帮助文档的说法,这些应用的运行必须在阿里云指定的位置运行,并且要受到全面的数据安全管控,另外最关键的是这些应用在上到市场前必须要经过阿里云的严格检测和评审,这有些像水果家App Store的态度。

    丰富的第三方应用更容易满足客户五花八门的管理类需求,阿里云本身则可以更聚焦于云平台核心功能的完善和升级,毕竟应用越丰富,平台的价值也越大。

    ​工具应用中心会成为下一个App Stroe么?

    ]]>
    解决问题 1474 个,Flink 1.11 究竟有哪些易用性上的改善? Fri, 20 Jun 2025 02:20:33 +0800 作者 | 王治江,Apache Flink PMC

    7月7日,Flink 1.11.0 正式发布了,作为这个版本的 release manager 之一,我想跟大家分享一下其中的经历感受以及一些代表性 feature 的解读。在进入深度解读前,我们先简单了解下社区发布的一般流程,帮助大家更好的理解和参与 Flink 社区的工作。

    • 首先在每个版本的规划初期,会从志愿者中选出 1-2 名作为 Release Manager。1.11.0 版本我作为中国这边的 Release Manager,同时还有一名来自 Ververica 的 Piotr Nowojski 作为德国方的 Release Manager,这在某种程度上也说明中国的开发者和贡献度在整个社区的占比很重要。
    • 接下来会进行这个版本的 Feature Kickoff。在一些大的方向上,社区的规划周期可能比较久,会分阶段、分步骤跨越多个版本完成,确保质量。每个版本的侧重点也会有所不同,比如前两个版本侧重于批处理的加强,而这个版本更侧重于流处理易用性的提升。社区规划的 Feature 列表会在邮件列表中发起讨论,以收集更多的用户/开发者意见和反馈。
    • 一般的开发周期为 2-3 个月时间,提前会明确规划出大概的 Feature Freeze 时间,之后进行 Release Candidate 的发布和测试、以及 Bug Fix。一般经过几轮的迭代周期后会正式投票通过一个相对稳定的 Candidate 版本,然后基于这个版本正式发布。

    Flink 1.11.0 从 3 月初的功能规划到 7 月初的正式发布,历经了差不多 4 个月的时间,对 Flink 的生态、易用性、生产可用性、稳定性等方面都进行了增强和改善,下面将一一跟大家分享。

    一 综述

    Flink 1.11.0 从 Feature 冻结后发布了 4 次 Candidate 才最终通过。经统计,一共有 236 个贡献者参与了这次版本开发,解决了 1474 个 Jira 问题,涉及 30 多个 FLIP,提交了 2325 个 Commit。

    1.jpg
    2.jpg

    纵观近五次版本发布,可以看出从 1.9.0 开始 Flink 进入了一个快速发展阶段,各个维度指标相比之前都有了几乎翻倍的提高。也是从 1.9.0 开始阿里巴巴内部的 Blink 项目开始被开源 Flink 整合,到 1.10.0 经过两个大版本已经全部整合完毕,对 Flink 从生态建设、功能性、性能和生产稳定性上都有了大幅的增强。

    Flink 1.11.0 版本的最初定位是重点解决易用性问题,提升用户业务的生产使用体验,整体上不做大的架构调整和功能开发,倾向于快速迭代的小版本开发。但是从上面统计的各个指标来看,所谓的“小版本”在各个维度的数据也丝毫不逊色于前两个大版本,解决问题的数量和参与的贡献者人数也在持续增加,其中来自中国的贡献者比例达到 62%。

    下面我们会深度剖析 Flink 1.11.0 带来了哪些让大家期待已久的特性,从用户直接使用的 API 层一直到执行引擎层,我们都会选择一些有代表性的 Feature 从不同维度解读,更完整的 Feature 列表请大家关注发布的 Release Blog。

    二 生态完善和易用性提升

    这两个维度在某种程度上是相辅相成的,很难严格区分开,生态兼容上的缺失常常造成使用上的不便,提升易用性的过程往往也是不断完善相关生态的过程。在这方面用户感知最明显的应该就是 Table & SQL API 层面的使用。

    1 Table & SQL 支持 Change Data Capture(CDC)

    CDC 被广泛使用在复制数据、更新缓存、微服务间同步数据、审计日志等场景,很多公司都在使用开源的 CDC 工具,如 MySQL CDC。通过 Flink 支持在 Table & SQL 中接入和解析 CDC 是一个强需求,在过往的很多讨论中都被提及过,可以帮助用户以实时的方式处理 Changelog 流,进一步扩展 Flink 的应用场景,例如把 MySQL 中的数据同步到 PG 或 ElasticSearch 中,低延时的 Temporal Join 一个 Changelog 等。

    除了考虑到上面的真实需求,Flink 中定义的“Dynamic Table”概念在流上有两种模型:Append 模式和 Update 模式。通过 Append 模式把流转化为“Dynamic Table”在之前的版本中已经支持,因此在 1.11.0 中进一步支持 Update 模式也从概念层面完整的实现了“Dynamic Table”。

    3.jpg

    为了支持解析和输出 Changelog,如何在外部系统和 Flink 系统之间编解码这些更新操作是首要解决的问题。考虑到 Source 和 Sink 是衔接外部系统的一个桥梁,因此 FLIP-95 在定义全新的 Table Source 和 Table Sink 接口时解决了这个问题。

    在公开的 CDC 调研报告中,Debezium 和 Canal 是用户中最流行使用的 CDC 工具,这两种工具用来同步 Changelog 到其它的系统中,如消息队列。据此,FLIP-105 首先支持了 Debezium 和 Canal 这两种格式,而且 Kafka Source 也已经可以支持解析上述格式并输出更新事件,在后续的版本中会进一步支持 Avro(Debezium) 和 Protobuf(Canal)。

    CREATE TABLE my_table (  
    ...) WITH (  
    'connector'='...', -- e.g. 'kafka'  
    'format'='debezium-json',  
    'debezium-json.schema-include'='true' -- default: false (Debezium can be configured to include or exclude the message schema)  
    'debezium-json.ignore-parse-errors'='true' -- default: false
    );

    2 Table & SQL 支持 JDBC Catalog

    1.11.0 之前,用户如果依赖 Flink 的 Source/Sink 读写关系型数据库或读取 Changelog 时,必须要手动创建对应的 Schema。而且当数据库中的 Schema 发生变化时,也需要手动更新对应的 Flink 作业以保持一致和类型匹配,任何不匹配都会造成运行时报错使作业失败。用户经常抱怨这个看似冗余且繁琐的流程,体验极差。

    实际上对于任何和 Flink 连接的外部系统都可能有类似的上述问题,在 1.11.0 中重点解决了和关系型数据库对接的这个问题。FLIP-93 提供了 JDBC catalog 的基础接口以及 Postgres catalog 的实现,这样方便后续实现与其它类型的关系型数据库的对接。

    1.11.0 版本后,用户使用 Flink SQL 时可以自动获取表的 Schema 而不再需要输入 DDL。除此之外,任何 Schema 不匹配的错误都会在编译阶段提前进行检查报错,避免了之前运行时报错造成的作业失败。这是提升易用性和用户体验的一个典型例子。

    3 Hive 实时数仓

    从 1.9.0 版本开始 Flink 从生态角度致力于集成 Hive,目标打造批流一体的 Hive 数仓。经过前两个版本的迭代,已经达到了 Batch 兼容且生产可用,在 TPC-DS 10T Benchmark 下性能达到 Hive 3.0 的 7 倍以上。

    1.11.0 在 Hive 生态中重点实现了实时数仓方案,改善了端到端流式 ETL 的用户体验,达到了批流一体 Hive 数仓的目标。同时在兼容性、性能、易用性方面也进一步进行了加强。

    在实时数仓的解决方案中,凭借 Flink 的流式处理优势做到实时读写 Hive:

    • Hive 写入:FLIP-115 完善扩展了 FileSystem Connector 的基础能力和实现,Table/SQL 层的 sink 可以支持各种格式(CSV、Json、Avro、Parquet、ORC),而且支持 Hive Table 的所有格式。
    • Partition 支持:数据导入 Hive 引入 Partition 提交机制来控制可见性,通过sink.partition-commit.trigger 控制 Partition 提交的时机,通过 sink.partition-commit.policy.kind 选择提交策略,支持 SUCCESS 文件和 Metastore 提交。
    • Hive 读取:实时化的流式读取 Hive,通过监控 Partition 生成增量读取新 Partition,或者监控文件夹内新文件生成来增量读取新文件。

    在 Hive 可用性方面的提升:

    • FLIP-123 通过 Hive Dialect 为用户提供语法兼容,这样用户无需在 Flink 和 Hive 的 CLI 之间切换,可以直接迁移 Hive 脚本到 Flink 中执行。
    • 提供 Hive 相关依赖的内置支持,避免用户自己下载所需的相关依赖。现在只需要单独下载一个包,配置 HADOOP_CLASSPATH 就可以运行。

    在 Hive 性能方面,1.10.0 中已经支持了 ORC(Hive 2+)的向量化读取,1.11.0 中我们补全了所有版本的 Parquet 和 ORC 向量化支持来提升性能。

    4 全新 Source API

    前面也提到过,Source 和 Sink 是 Flink 对接外部系统的一个桥梁,对于完善生态、可用性及端到端的用户体验是很重要的环节。社区早在一年前就已经规划了 Source 端的彻底重构,从 FLIP-27 的 ID 就可以看出是很早的一个 Feature。但是由于涉及到很多复杂的内部机制和考虑到各种 Source Connector 的实现,设计上需要考虑的很全面。从 1.10.0 就开始做 POC 的实现,最终赶上了 1.11.0 版本的发布。

    先简要回顾下 Source 之前的主要问题:

    • 对用户而言,在 Flink 中改造已有的 Source 或者重新实现一个生产级的 Source Connector 不是一件容易的事情,具体体现在没有公共的代码可以复用,而且需要理解很多 Flink 内部细节以及实现具体的 Event Time 分配、Watermark 产出、Idleness 监测、线程模型等。
    • 批和流的场景需要实现不同的 Source。
    • Partitions/Splits/Shards 概念在接口中没有显式表达,比如 Split 的发现逻辑和数据消费都耦合在 Source Sunction 的实现中,这样在实现 Kafka 或 Kinesis 类型的 Source 时增加了复杂性。
    • 在 Runtime 执行层,Checkpoint 锁被 Source Function 抢占会带来一系列问题,框架很难进行优化。

    FLIP-27 在设计时充分考虑了上述的痛点:

    4.jpg

    • 首先在 Job Manager 和 Task Manager 中分别引入两种不同的组件 Split Enumerator 和 Source Reader,解耦 Split 发现和对应的消费处理,同时方便随意组合不同的策略。比如现有的 Kafka Connector 中有多种不同的 Partition 发现策略和实现耦合在一起,在新的架构下,我们只需要实现一种 Source Reader,就可以适配多种 Split Enumerator 的实现来对应不同的 Partition 发现策略。
    • 在新架构下实现的 Source Connector 可以做到批流统一,唯一的小区别是对批场景的有限输入,Split Enumerator 会产出固定数量的 Split 集合并且每个 Split 都是有限数据集;对于流场景的无限输入,Split Enumerator 要么产出无限多的 Split 或者 Split 自身是无限数据集。
    • 复杂的 Timestamp Assigner 以及 Watermark Generator 透明的内置在 Source Reader 模块内运行,对用户来说是无感知的。这样用户如果想实现新的 Source Connector,一般不再需要重复实现这部分功能。

    目前 Flink 已有的 Source Connector 会在后续的版本中基于新架构来重新实现,Legacy Source 也会继续维护几个版本保持兼容性,用户也可以按照 Release 文档中的说明来尝试体验新 Source 的开发。

    5 PyFlink 生态

    众所周知,Python 语言在机器学习和数据分析领域有着广泛的使用。Flink 从 1.9.0 版本开始发力兼容 Python 生态,Python 和 Flink 合力为 PyFlink,把 Flink 的实时分布式处理能力输出给 Python 用户。前两个版本 PyFlink 已经支持了 Python Table API 和 UDF,在 1.11.0 中扩大对 Python 生态库 Pandas 的支持以及和 SQL DDL/Client 的集成,同时 Python UDF 性能有了极大的提升。

    具体来说,之前普通的 Python UDF 每次调用只能处理一条数据,而且在 Java 端和 Python 端都需要序列化/反序列化,开销很大。1.11.0 中 Flink 支持在 Table & SQL 作业中自定义和使用向量化 Python UDF,用户只需要在 UDF 修饰中额外增加一个参数 udf_type=“pandas” 即可。这样带来的好处是:

    • 每次调用可以处理 N 条数据。
    • 数据格式基于 Apache Arrow,大大降低了 Java、Python 进程之间的序列化/反序列化开销。
    • 方便 Python 用户基于 Numpy 和 Pandas 等数据分析领域常用的 Python 库,开发高性能的 Python UDF。

    除此之外,1.11.0 中 PyFlink 还支持:

    • PyFlink table 和 Pandas DataFrame 之间无缝切换(FLIP-120),增强 Pandas 生态的易用性和兼容性。
    • Table & SQL 中可以定义和使用 Python UDTF(FLINK-14500),不再必需 Java/Scala UDTF。
    • Cython 优化 Python UDF 的性能(FLIP-121),对比 1.10.0 可以提升 30 倍。
    • Python UDF 中用户自定义 Metric(FLIP-112),方便监控和调试 UDF 的执行。

    上述解读的都是侧重 API 层面,用户开发作业可以直接感知到的易用性的提升。下面我们看看执行引擎层在 1.11.0 中都有哪些值得关注的变化。

    三 生产可用性和稳定性提升

    1 支持 Application 模式和 Kubernetes 增强

    1.11.0 版本前,Flink 主要支持如下两种模式运行:

    • Session 模式:提前启动一个集群,所有作业都共享这个集群的资源运行。优势是避免每个作业单独启动集群带来的额外开销,缺点是隔离性稍差。如果一个作业把某个 Task Manager(TM)容器搞挂,会导致这个容器内的所有作业都跟着重启。虽然每个作业有自己独立的 Job Manager(JM)来管理,但是这些 JM 都运行在一个进程中,容易带来负载上的瓶颈。
    • Per-job 模式:为了解决 Session 模式隔离性差的问题,每个作业根据资源需求启动独立的集群,每个作业的 JM 也是运行在独立的进程中,负载相对小很多。

    以上两种模式的共同问题是需要在客户端执行用户代码,编译生成对应的 Job Graph 提交到集群运行。在这个过程需要下载相关 Jar 包并上传到集群,客户端和网络负载压力容易成为瓶颈,尤其当一个客户端被多个用户共享使用。

    1.11.0 中引入了 Application 模式(FLIP-85)来解决上述问题,按照 Application 粒度来启动一个集群,属于这个 Application 的所有 Job 在这个集群中运行。核心是 Job Graph 的生成以及作业的提交不在客户端执行,而是转移到 JM 端执行,这样网络下载上传的负载也会分散到集群中,不再有上述 Client 单点上的瓶颈。

    用户可以通过 bin/flink run-application 来使用 Application 模式,目前 Yarn 和 Kubernetes(K8s)都已经支持这种模式。Yarn application 会在客户端将运行作业需要的依赖都通过 Yarn Local Resource 传递到 JM。K8s Application 允许用户构建包含用户 Jar 与依赖的镜像,同时会根据作业自动创建 TM,并在结束后销毁整个集群,相比 Session 模式具有更好的隔离性。K8s 不再有严格意义上的 Per-Job 模式,Application 模式相当于 Per-Job 在集群进行提交作业的实现。

    除了支持 Application 模式,Flink 原生 K8s 在 1.11.0 中还完善了很多基础的功能特性(FLINK-14460),以达到生产可用性的标准。例如 Node Selector、Label、Annotation、Toleration 等。为了更方便的与 Hadoop 集成,也支持根据环境变量自动挂载 Hadoop 配置的功能。

    2 Checkpoint & Savepoint 优化

    Checkpoint 和 Savepoint 机制一直是 Flink 保持先进性的核心竞争力之一,社区在这个领域的改动很谨慎,最近的几个大版本中几乎没有大的功能和架构上的调整。在用户邮件列表中,我们经常能看到用户反馈和抱怨的相关问题:比如 Checkpoint 长时间做不出来失败,Savepoint 在作业重启后不可用等等。1.11.0 有选择的解决了一些这方面的常见问题,提高生产可用性和稳定性。

    1.11.0 之前, Savepoint 中 Meta 数据和 State 数据分别保存在两个不同的目录中,这样如果想迁移 State 目录很难识别这种映射关系,也可能导致目录被误删除,对于目录清理也同样有麻烦。1.11.0 把两部分数据整合到一个目录下,这样方便整体转移和复用。另外,之前 Meta 引用 State 采用的是绝对路径,这样 State 目录迁移后路径发生变化也不可用,1.11.0 把 State 引用改成了相对路径解决了这个问题(FLINK-5763),这样 Savepoint 的管理维护、复用更加灵活方便。

    实际生产环境中,用户经常遭遇 Checkpoint 超时失败、长时间不能完成带来的困扰。一旦作业 failover 会造成回放大量的历史数据,作业长时间没有进度,端到端的延迟增加。1.11.0 从不同维度对 Checkpoint 的优化和提速做了改进,目标实现分钟甚至秒级的轻量型 Checkpoint。

    首先,增加了 Checkpoint Coordinator 通知 Task 取消 Checkpoint 的机制(FLINK-8871),这样避免 Task 端还在执行已经取消的 Checkpoint 而对系统带来不必要的压力。同时 Task 端放弃已经取消的 Checkpoint,可以更快的参与执行 Coordinator 新触发的 Checkpoint,某种程度上也可以避免新 Checkpoint 再次执行超时而失败。这个优化也对后面默认开启 Local Recovery 提供了便利,Task 端可以及时清理失效 Checkpoint 的资源。

    其次,在反压场景下,整个数据链路堆积了大量 Buffer,导致 Checkpoint Barrier 排在数据 Buffer 后面,不能被 Task 及时处理对齐,也就导致了 Checkpoint 长时间不能执行。1.11.0 中从两个维度对这个问题进行解决:

    1)尝试减少数据链路中的 Buffer 总量(FLINK-16428),这样 Checkpoint Barrier 可以尽快被处理对齐。

    • 上游输出端控制单个 Sub Partition 堆积 Buffer 的最大阈值(Backlog),避免负载不均场景下单个链路上堆积大量 Buffer。
    • 在不影响网络吞吐性能的情况下合理修改上下游默认的 Buffer 配置。
    • 上下游数据传输的基础协议进行了调整,允许单个数据链路可以配置 0 个独占 Buffer 而不死锁,这样总的 Buffer 数量和作业并发规模解耦。根据实际需求在吞吐性能和 Checkpoint 速度两者之间权衡,自定义 Buffer 配比。

    这个优化有一部分工作已经在 1.11.0 中完成,剩余部分会在下个版本继续推进完成。

    2)实现了全新的 Unaligned Checkpoint 机制(FLIP-76)从根本上解决了反压场景下 Checkpoint Barrier 对齐的问题。实际上这个想法早在 1.10.0 版本之前就开始酝酿设计,由于涉及到很多模块的大改动,实现机制和线程模型也很复杂。我们实现了两种不同方案的原型 POC 进行了测试、性能对比,确定了最终的方案,因此直到 1.11.0 才完成了 MVP 版本,这也是 1.11.0 中执行引擎层唯一的一个重量级 Feature。其基本思想可以概括为:

    • Checkpoint Barrier 跨数据 Buffer 传输,不在输入输出队列排队等待处理,这样就和算子的计算能力解耦,Barrier 在节点之间的传输只有网络延时,可以忽略不计。
    • 每个算子多个输入链路之间不需要等待 Barrier 对齐来执行 Checkpoint,第一个到的 Barrier 就可以提前触发 Checkpoint,这样可以进一步提速 Checkpoint,不会因为个别链路的延迟而影响整体。
    • 为了和之前 Aligned Checkpoint 的语义保持一致,所有未被处理的输入输出数据 Buffer 都将作为 Channel State 在 Checkpoint 执行时进行快照持久化,在 Failover 时连同 Operator State 一同进行恢复。换句话说,Aligned 机制保证的是 Barrier 前面所有数据必须被处理完,状态实时体现到 Operator State 中;而 Unaligned 机制把 Barrier 前面的未处理数据所反映的 Operator State 延后到 Failover Restart 时通过 Channel State 回放进行体现,从状态恢复的角度来说最终都是一致的。注意这里虽然引入了额外的 In-Flight Buffer 的持久化,但是这个过程实际是在 Checkpoint 的异步阶段完成的,同步阶段只是进行了轻量级的 Buffer 引用,所以不会过多占用算子的计算时间而影响吞吐性能。

    5.jpg

    Unaligned Checkpoint 在反压严重的场景下可以明显加速 Checkpoint 的完成时间,因为它不再依赖于整体的计算吞吐能力,而和系统的存储性能更加相关,相当于计算和存储的解耦。但是它的使用也有一定的局限性,它会增加整体 State 的大小,对存储 IO 带来额外的开销,因此在 IO 已经是瓶颈的场景下就不太适合使用 Unaligned Checkpoint 机制。

    1.11.0 中 Unaligned Checkpoint 还没有作为默认模式,需要用户手动配置来开启,并且只在 Exactly-Once 模式下生效。但目前还不支持 Savepoint 模式,因为 Savepoint 涉及到作业的 Rescale 场景,Channel State 目前还不支持 State 拆分,在后面的版本会进一步支持,所以 Savepoint 目前还是会使用之前的 Aligned 模式,在反压场景下有可能需要很长时间才能完成。

    四 总结

    Flink 1.11.0 版本的开发过程中,我们看到越来越多来自中国的贡献者参与到核心功能的开发中,见证了 Flink 在中国的生态发展越来越繁荣,比如来自腾讯公司的贡献者参与了 K8s、Checkpoint 等功能开发,来自字节跳动公司的贡献者参与了 Table & SQL 层以及引擎网络层的一些开发。希望更多的公司能够参与到 Flink 开源社区中,分享在不同领域的经验,使 Flink 开源技术一直保持先进性,能够普惠到更多的受众。

    经过 1.11.0 “小版本”的短暂调整,Flink 正在酝酿下一个大版本的 Feature,相信一定会有很多重量级的特性登场,让我们拭目以待!

    ]]>
    Flink 1.11 新特性之 SQL Hive Streaming 简单示例 Fri, 20 Jun 2025 02:20:33 +0800 7月7日,Flink 1.11 版本发布,与 1.10 版本相比,1.11 版本最为显著的一个改进是 Hive Integration 显著增强,也就是真正意义上实现了基于 Hive 的流批一体。

    本文用简单的本地示例来体验 Hive Streaming 的便利性并跟大家分享体验的过程以及我的心得,希望对大家上手使用有所帮助。

    添加相关依赖

    测试集群上的 Hive 版本为 1.1.0,Hadoop 版本为 2.6.0,Kafka 版本为 1.0.1。

    <properties>
      <scala.bin.version>2.11</scala.bin.version>
      <flink.version>1.11.0</flink.version>
      <flink-shaded-hadoop.version>2.6.5-10.0</flink-shaded-hadoop.version>
      <hive.version>1.1.0</hive.version>
    </properties>
    
    <dependencies>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-scala_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-common</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-api-scala-bridge_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-planner-blink_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-connector-hive_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-sql-connector-kafka_${scala.bin.version}</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-json</artifactId>
        <version>${flink.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-shaded-hadoop-2-uber</artifactId>
        <version>${flink-shaded-hadoop.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.hive</groupId>
        <artifactId>hive-exec</artifactId>
        <version>${hive.version}</version>
      </dependency>

    另外,别忘了找到 hdfs-site.xml 和 hive-site.xml,并将其加入项目。

    创建执行环境

    Flink 1.11 的 Table/SQL API 中,FileSystem Connector 是靠增强版 StreamingFileSink 组件实现,在源码中名为 StreamingFileWriter。我们知道,只有在 Checkpoint 成功时,StreamingFileSink 写入的文件才会由 Pending 状态变成 Finished 状态,从而能够安全地被下游读取。所以,我们一定要打开 Checkpointing,并设定合理的间隔。

    1.jpg

    val streamEnv = StreamExecutionEnvironment.getExecutionEnvironment
    streamEnv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    streamEnv.setParallelism(3)
    
    val tableEnvSettings = EnvironmentSettings.newInstance()
        .useBlinkPlanner()
        .inStreamingMode()
        .build()
    val tableEnv = StreamTableEnvironment.create(streamEnv, tableEnvSettings)
    tableEnv.getConfig.getConfiguration.set(ExecutionCheckpointingOptions.CHECKPOINTING_MODE, CheckpointingMode.EXACTLY_ONCE)
    tableEnv.getConfig.getConfiguration.set(ExecutionCheckpointingOptions.CHECKPOINTING_INTERVAL, Duration.ofSeconds(20))

    注册 HiveCatalog

    val catalogName = "my_catalog"
    val catalog = new HiveCatalog(
      catalogName,              // catalog name
      "default",                // default database
      "/Users/lmagic/develop",  // Hive config (hive-site.xml) directory
      "1.1.0"                   // Hive version
    )
    tableEnv.registerCatalog(catalogName, catalog)
    tableEnv.useCatalog(catalogName)

    创建 Kafka 流表

    Kafka Topic 中存储的是 JSON 格式的埋点日志,建表时用计算列生成事件时间与水印。1.11 版本 SQL Kafka Connector 的参数相比 1.10 版本有一定简化。

    tableEnv.executeSql("CREATE DATABASE IF NOT EXISTS stream_tmp")
    tableEnv.executeSql("DROP TABLE IF EXISTS stream_tmp.analytics_access_log_kafka")
    
    tableEnv.executeSql(
      """
        |CREATE TABLE stream_tmp.analytics_access_log_kafka (
        |  ts BIGINT,
        |  userId BIGINT,
        |  eventType STRING,
        |  fromType STRING,
        |  columnType STRING,
        |  siteId BIGINT,
        |  grouponId BIGINT,
        |  partnerId BIGINT,
        |  merchandiseId BIGINT,
        |  procTime AS PROCTIME(),
        |  eventTime AS TO_TIMESTAMP(FROM_UNIXTIME(ts / 1000,'yyyy-MM-dd HH:mm:ss')),
        |  WATERMARK FOR eventTime AS eventTime - INTERVAL '15' SECOND
        |) WITH (
        |  'connector' = 'kafka',
        |  'topic' = 'ods_analytics_access_log',
        |  'properties.bootstrap.servers' = 'kafka110:9092,kafka111:9092,kafka112:9092'
        |  'properties.group.id' = 'flink_hive_integration_exp_1',
        |  'scan.startup.mode' = 'latest-offset',
        |  'format' = 'json',
        |  'json.fail-on-missing-field' = 'false',
        |  'json.ignore-parse-errors' = 'true'
        |)
      """.stripMargin
    )

    前面已经注册了 HiveCatalog,故在 Hive 中可以观察到创建的 Kafka 流表的元数据(注意该表并没有事实上的列)。

    hive> DESCRIBE FORMATTED stream_tmp.analytics_access_log_kafka;
    OK
    # col_name              data_type               comment
    
    
    # Detailed Table Information
    Database:               stream_tmp
    Owner:                  null
    CreateTime:             Wed Jul 15 18:25:09 CST 2020
    LastAccessTime:         UNKNOWN
    Protect Mode:           None
    Retention:              0
    Location:               hdfs://sht-bdmq-cls/user/hive/warehouse/stream_tmp.db/analytics_access_log_kafka
    Table Type:             MANAGED_TABLE
    Table Parameters:
        flink.connector         kafka
        flink.format            json
        flink.json.fail-on-missing-field    false
        flink.json.ignore-parse-errors  true
        flink.properties.bootstrap.servers  kafka110:9092,kafka111:9092,kafka112:9092
        flink.properties.group.id   flink_hive_integration_exp_1
        flink.scan.startup.mode latest-offset
        flink.schema.0.data-type    BIGINT
        flink.schema.0.name     ts
        flink.schema.1.data-type    BIGINT
        flink.schema.1.name     userId
        flink.schema.10.data-type   TIMESTAMP(3)
        flink.schema.10.expr    TO_TIMESTAMP(FROM_UNIXTIME(`ts` / 1000, 'yyyy-MM-dd HH:mm:ss'))
        flink.schema.10.name    eventTime
        flink.schema.2.data-type    VARCHAR(2147483647)
        flink.schema.2.name     eventType
        # 略......
        flink.schema.9.data-type    TIMESTAMP(3) NOT NULL
        flink.schema.9.expr     PROCTIME()
        flink.schema.9.name     procTime
        flink.schema.watermark.0.rowtime    eventTime
        flink.schema.watermark.0.strategy.data-type TIMESTAMP(3)
        flink.schema.watermark.0.strategy.expr  `eventTime` - INTERVAL '15' SECOND
        flink.topic             ods_analytics_access_log
        is_generic              true
        transient_lastDdlTime   1594808709
    
    # Storage Information
    SerDe Library:          org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
    InputFormat:            org.apache.hadoop.mapred.TextInputFormat
    OutputFormat:           org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat
    Compressed:             No
    Num Buckets:            -1
    Bucket Columns:         []
    Sort Columns:           []
    Storage Desc Params:
        serialization.format    1
    Time taken: 1.797 seconds, Fetched: 61 row(s)

    创建 Hive 表

    Flink SQL 提供了兼容 HiveQL 风格的 DDL,指定 SqlDialect.HIVE 即可( DML 兼容还在开发中)。

    为了方便观察结果,以下的表采用了天/小时/分钟的三级分区,实际应用中可以不用这样细的粒度(10分钟甚至1小时的分区可能更合适)。

    tableEnv.getConfig.setSqlDialect(SqlDialect.HIVE)
    
    tableEnv.executeSql("CREATE DATABASE IF NOT EXISTS hive_tmp")
    tableEnv.executeSql("DROP TABLE IF EXISTS hive_tmp.analytics_access_log_hive")
    
    tableEnv.executeSql(
      """
        |CREATE TABLE hive_tmp.analytics_access_log_hive (
        |  ts BIGINT,
        |  user_id BIGINT,
        |  event_type STRING,
        |  from_type STRING,
        |  column_type STRING,
        |  site_id BIGINT,
        |  groupon_id BIGINT,
        |  partner_id BIGINT,
        |  merchandise_id BIGINT
        |) PARTITIONED BY (
        |  ts_date STRING,
        |  ts_hour STRING,
        |  ts_minute STRING
        |) STORED AS PARQUET
        |TBLPROPERTIES (
        |  'sink.partition-commit.trigger' = 'partition-time',
        |  'sink.partition-commit.delay' = '1 min',
        |  'sink.partition-commit.policy.kind' = 'metastore,success-file',
        |  'partition.time-extractor.timestamp-pattern' = '$ts_date $ts_hour:$ts_minute:00'
        |)
      """.stripMargin
    )

    Hive 表的参数复用了 SQL FileSystem Connector 的相关参数,与分区提交(Partition Commit)密切相关。仅就上面出现的4个参数简单解释一下。

    • sink.partition-commit.trigger:触发分区提交的时间特征。默认为 processing-time,即处理时间,很显然在有延迟的情况下,可能会造成数据分区错乱。所以这里使用 partition-time,即按照分区时间戳(即分区内数据对应的事件时间)来提交。
    • partition.time-extractor.timestamp-pattern:分区时间戳的抽取格式。需要写成 yyyy-MM-dd HH:mm:ss 的形式,并用 Hive 表中相应的分区字段做占位符替换。显然,Hive 表的分区字段值来自流表中定义好的事件时间,后面会看到。
    • sink.partition-commit.delay:触发分区提交的延迟。在时间特征设为 partition-time 的情况下,当水印时间戳大于分区创建时间加上此延迟时,分区才会真正提交。此值最好与分区粒度相同,例如若 Hive 表按1小时分区,此参数可设为 1 h,若按 10 分钟分区,可设为 10 min。
    • sink.partition-commit.policy.kind:分区提交策略,可以理解为使分区对下游可见的附加操作。 metastore 表示更新 Hive Metastore 中的表元数据, success-file 则表示在分区内创建 _SUCCESS 标记文件。

    当然,SQL FileSystem Connector 的功能并不限于此,还有很大自定义的空间(如可以自定义分区提交策略以合并小文件等)。具体可参见官方文档。

    https://ci.apache.org/projects/flink/flink-docs-release-1.11/dev/table/connectors/filesystem.html#streaming-sink

    流式写入 Hive

    注意将流表中的事件时间转化为 Hive 的分区。

    tableEnv.getConfig.setSqlDialect(SqlDialect.DEFAULT)
    tableEnv.executeSql(
      """
        |INSERT INTO hive_tmp.analytics_access_log_hive
        |SELECT
        |  ts,userId,eventType,fromType,columnType,siteId,grouponId,partnerId,merchandiseId,
        |  DATE_FORMAT(eventTime,'yyyy-MM-dd'),
        |  DATE_FORMAT(eventTime,'HH'),
        |  DATE_FORMAT(eventTime,'mm')
        |FROM stream_tmp.analytics_access_log_kafka
        |WHERE merchandiseId > 0
      """.stripMargin
    )

    来观察一下流式 Sink 的结果吧。

    2.jpg

    上文设定的 Checkpoint Interval 是 20 秒,可以看到,上图中的数据文件恰好是以 20 秒的间隔写入的。由于并行度为 3,所以每次写入会生成 3 个文件。分区内所有数据写入完毕后,会同时生成 _SUCCESS 文件。如果是正在写入的分区,则会看到 .inprogress 文件。

    通过 Hive 查询一下,确定数据的时间无误。

    hive> SELECT from_unixtime(min(cast(ts / 1000 AS BIGINT))),from_unixtime(max(cast(ts / 1000 AS BIGINT)))
        > FROM hive_tmp.analytics_access_log_hive
        > WHERE ts_date = '2020-07-15' AND ts_hour = '23' AND ts_minute = '23';
    OK
    2020-07-15 23:23:00 2020-07-15 23:23:59
    Time taken: 1.115 seconds, Fetched: 1 row(s)

    流式读取 Hive

    要将 Hive 表作为流式 Source,需要启用 Dynamic Table Options,并通过 Table Hints 来指定 Hive 数据流的参数。以下是简单地通过 Hive 计算商品 PV 的例子。

    tableEnv.getConfig.getConfiguration.setBoolean(TableConfigOptions.TABLE_DYNAMIC_TABLE_OPTIONS_ENABLED, true)
    
    val result = tableEnv.sqlQuery(
      """
         |SELECT merchandise_id,count(1) AS pv
         |FROM hive_tmp.analytics_access_log_hive
         |/*+ OPTIONS(
         |  'streaming-source.enable' = 'true',
         |  'streaming-source.monitor-interval' = '1 min',
         |  'streaming-source.consume-start-offset' = '2020-07-15 23:30:00'
         |) */
         |WHERE event_type = 'shtOpenGoodsDetail'
         |AND ts_date >= '2020-07-15'
         |GROUP BY merchandise_id
         |ORDER BY pv DESC LIMIT 10
       """.stripMargin
    )
    
    result.toRetractStream[Row].print().setParallelism(1)
    streamEnv.execute()

    三个 Table Hint 参数的含义解释如下。

    • streaming-source.enable:设为 true,表示该 Hive 表可以作为 Source。
    • streaming-source.monitor-interval:感知 Hive 表新增数据的周期,以上设为 1 分钟。对于分区表而言,则是监控新分区的生成,以增量读取数据。
    • streaming-source.consume-start-offset:开始消费的时间戳,同样需要写成 yyyy-MM-dd HH:mm:ss 的形式。

    更加具体的说明仍然可参见官方文档。

    https://links.jianshu.com/go?to=https%3A%2F%2Fci.apache.org%2Fprojects%2Fflink%2Fflink-docs-release-1.11%2Fdev%2Ftable%2Fhive%2Fhive_streaming.html%23streaming-reading

    最后,由于 SQL 语句中有 ORDER BY 和 LIMIT 逻辑,所以需要调用 toRetractStream() 方法转化为回撤流,即可输出结果。

    The End

    Flink 1.11 的 Hive Streaming 功能大大提高了 Hive 数仓的实时性,对 ETL 作业非常有利,同时还能够满足流式持续查询的需求,具有一定的灵活性。

    感兴趣的同学也可以自己上手测试。

    ]]>
    【北京天源迪科信息技术有限公司】- 招聘职位详情(社招) Fri, 20 Jun 2025 02:20:33 +0800 北京天源迪科信息技术有限公司介绍

    北京天源迪科信息技术有限公司(以下简称:北京天源迪科)成立于2011年,为天源迪科全资子公司。北京天源迪科秉承天源迪科集团公司的发展主旨,是一家集产品研发、工程实施、系统集成、系统运维、业务咨询、增值运营为一体的综合性软件公司。在北京中关村及亦庄核心地段有两处办公区域,拥有员工800多人。北京天源迪科经过这些年的快速发展,公司以电信运营商业务运营支撑软件和服务为根基,大力发展云计算、大数据、人工智能、物联网等先进技术,业务领域已经拓展到运营商、军工、军队、公安、政府、金融、新能源等十多个行业,已形成跨行业、多盈利模式的业务布局。

    公司目前已拥有300多项软件著作权及发明专利,并与华为、阿里巴巴、腾讯、中电科等业界知名企业结成战略合作伙伴关系。
    未来,北京天源迪科继续秉承“诚信、专业、协作、创新”的精神,将坚持走自主创新、科技强军、安全可控、合作发展的道路,携手用户智慧创新,更好地服务于军队和社会。
    

    职位1: 军工项目经理

    Base地点: 北京
    岗位职责:
    安排项目计划,跟踪项目进度;
    收集客户的潜在需求,对客户的需求进行分析,输出需要分析及方案设计文档;
    负责现场项目的日常运维及系统升级相关任务安排;
    具体要求:
    熟悉oracle/mysql数据库,了解主要数据结构,SQL优化及数据库管理工具;
    熟悉spring boot架构,对多线程应用的项目应用经验;
    熟练使用禅道,project等常见项目管理以及WBS工具;
    熟悉Axsure等设计工具的使用;
    其他要求:
    至少5年项目管理经验,有大型IT应用系统实施及项目管理经验;有军工项目经验优先,有国产化应用系统使用经验者优先;
    对客户的需求敏感,能够自主收集客户的潜在需求,
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:1人;持阿里云认证优先

    职位2:JAVA开发(中关村)

    Base地点: 北京
    岗位职责:
    1、负责部门核心项目、模块需求分析和代码研发;
    2、参与架构设计、分析和功能设计工作;
    3、承担项目、模块核心功能、公共核心模块的代码编写;接口规范制定,确保负责的项目、模块进度和质量;
    4、主导、参与技术难题攻关,持续提升核心系统的稳定性及性能。
    5、负责相关项目、模块的研发交付,项目架构设计文档的编写
    具体要求:
    1、计算机及相关专业本科及以上学历;
    2、3年以上JAVA开发经验,1年以上分布式系统Java开发的经验;
    3、具有良好的团队协作能力和沟通能力,表达能力强,工作积极主动、有责任心;
    4、JAVA基础知识扎实,深刻理解多线程、集合等基础框架,熟悉多线程及高性能的设计与编码及性能调优;
    5、熟悉主流数据库系统(如MySQL,PG)的管理和使用;
    6、熟悉分布式系统的设计和应用,熟悉分布式、缓存、消息等机制,在大中型系统开发中担任过核心开发或架构师角色。
    7、熟练掌握 SSM 、Spring Boot等开源框架,具有扩展框架的能力;
    8、熟练掌握 JAVA 并发(AIO、NIO等)编程,了解HTTP、TCP/IP协议;
    9、RabbitMQ、ActiveMQ和Kafaka等MQ至少熟悉一种;
    10、Redis、Memcached等缓存技术,至少熟悉一种;
    其他要求:
    熟悉bootstraps/vue/easyui/extjs前端框架者优先;
    具备良好的理解能力和文档表达能力,输出相关文档
    有项目技术管理经验者优先
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:2人;持阿里云认证优先

    职位3:JAVA开发(亦庄)

    Base地点: 北京
    岗位职责:
    1、主要从事java后台服务端业务逻辑的功能设计、开发和实现;
    2、参与需求分析及设计评审、文档编写;
    3、根据业务线项目需求,开发新系统及系统升级改造;
    4、根据产品功能模块设计,编码实现各模块功能,并确保开发质量与进度;
    5、工作态度积极、主动,善于学习,有团队合作精神,具有良好的理解能力与沟通能力。
    具体要求:
    1、本科及以上学历,3年以上java开发经验;
    2、精通java语言,掌握面向对象的编程方法及思想;熟悉多线程、熟练应用微服务框架进行应用系统的开发;
    3、有分布式数据库开发经验者优先;
    4、使用过flink流式计算框架、rocketmq分布式消息队列、redis分布式缓存技术应用项目者优先;
    5、掌握linux操作系统常用的操作命令; 有过DCOS或K8S经验者优先
    6、熟练掌握ORACLE或MYSQL数据库;
    7、能接受出差,具备较强的学习、分析问题解决问题的能力;良好的团队合作精神。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:2人;持阿里云认证优先

    职位4: JAVA开发(重庆)

    Base地点: 重庆
    岗位职责:
    1.系统需求分析、设计、实现
    2.高可用、高并发服务端系统、接口设计和开发
    3.根据客户端业务设计和实现可扩展的服务端系统
    4.与团队共同协作解决技术难题
    5.为产品设计提供技术评估
    6.关注互联网应用相关技术与信息
    具体要求:

    1. 本科及以上学历,计算机相关专业,2年以上工作经验;
      2、java基础扎实,熟悉io、多线程、集合等,熟悉分布式、缓存、消息、搜索等机制,熟悉Java web开发;

    3、熟悉Spring、Mybatis、Redis、MQ(基本开发框架)等常用框架;熟悉linux;
    4、精通Mysql、Oracle数据库或至少一种数据库,能进行数据库设计。
    5、熟悉分布式系统架构设计和实现,有大型分布式,高并发,高负载,高可用性系统设计开发经验优先。
    6、思路清晰,善于学习和思考,能独立分析和解决问题;
    7.、拥有良好的团队协作能力与抗压能力;
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:6人;持阿里云认证优先

    职位5:JAVA开发(合肥)

    Base地点: 合肥
    岗位职责:
    1、根据项目要求完成功能模块设计、编码、集成测试与接口联调,确保开发质量与进度;
    2、负责系统模块的优化、后续系统维护;
    3、负责相关项目文档编写;
    4、有电信项目开发经验优先考虑;
    5、2年或以上软件开发经验。
    具体要求:
    1、精通Java语言,掌握面向对象的编程方法及思想;熟悉J2EE体系,熟悉流行的MVC框架Struts2,Spring,Mybatis等;
    2、有Java Web实际项目开发经验,掌握AJAX、jQuery、WebService、Socket、XML等技术,有良好的编码习惯;
    3、良好的面向对象思想,熟悉设计模式;
    4、熟练掌握Oracle及MySql数据库,可编写复杂的SQL语句,有一定的SQL调优能力;
    5、熟悉Docker容器化技术,Mesos、Marathon、Kubernetes等分布式任务调度技术者优先;
    6、具备较强的学习、问题分析&解决能力,良好的团队合作精神。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:3人;持阿里云认证优先

    职位6: JAVA开发(郑州)

    Base地点: 郑州
    岗位职责:
    1、根据项目要求完成功能模块设计、编码、集成测试与接口联调,确保开发质量与进度;
    2、负责系统模块的优化、后续系统维护;
    3、负责相关项目文档编写;
    4、有电信项目开发经验优先考虑;
    5、2年或以上软件开发经验。
    具体要求:
    1、精通Java语言,掌握面向对象的编程方法及思想;熟悉J2EE体系,熟悉流行的MVC框架Struts2,Spring,Mybatis等;
    2、有Java Web实际项目开发经验,掌握AJAX、jQuery、WebService、Socket、XML等技术,有良好的编码习惯;
    3、良好的面向对象思想,熟悉设计模式;
    4、熟练掌握Oracle及MySql数据库,可编写复杂的SQL语句,有一定的SQL调优能力;
    5、熟悉Docker容器化技术,Mesos、Marathon、Kubernetes等分布式任务调度技术者优先;
    6、具备较强的学习、问题分析&解决能力,良好的团队合作精神。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:2人;持阿里云认证优先

    职位7: JAVA开发(南京)

    Base地点: 南京
    岗位职责:
    1.负责公司相关项目的设计、开发、单元测试、联调、集成测试等工作;
    2.项目方案及设计文档的编写;
    具体要求:
    1.全日制计算机相关专业本科或以上学历,计算机相关专业优先,1~3年Java开发工作经验;
    2.熟悉spring,Struct等主流框架,熟悉服务容器tomcat等的使用;
    3.熟练使用idea或其他集成开发工具,熟悉Linux系统及其命令;
    4.至少熟练使用一种关系数据库,如Mysql或Oracle;
    5.具有良好的团队合作意思,有强烈的责任心和积极主动的工作态度,较强的沟通能力和抗压能力;
    其他要求:
    具有分布式开发工作经验者优先;具有电商工作经验者优先。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:2人;持阿里云认证优先

    职位8: 运维(亦庄)

    Base地点: 北京
    岗位职责:
    可接受1-3个月的出差
    负责配合阿里云产品及相关业务应用的系统集成及维护
    安装部署维护各种主流开源中间件
    具体要求:
    计算机及相关专业本科以上学历,1年以上持续集成工作经验
    *熟悉持续集成jenkins安装部署、脚本编写,代码管理工具Gitlab ,拥有实际应用的相关经验
    *熟悉shell及python语言,拥有脚本编写经验
    *熟悉阿里云操作,至少管理过超过20台阿里云虚拟服务器
    *熟悉阿里云产品,包括SLB、RDS、OSS、Redis等
    熟悉CentOS Linux,安装部署及维护
    熟悉MySQL 安装部署及日常维护,熟悉SQL语言
    熟悉kubernetes容器平台,掌握部署安装及维护工作
    了解主流开源技术,如Nginx、LVS、keepalived、消息队列等,有维护经验
    了解zabbix或prometheus监控系统
    其他要求:
    熟悉云计算技术,拥有阿里云项目经验优先
    有阿里云相关认证优先
    做事思路清晰,具备文档撰写能力
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:2人;持阿里云认证优先

    职位9:需求/产品(深圳)

    Base地点: 深圳
    岗位职责:
    1、负责部门相关项目的需求梳理工作,形成产品方案;
    2、对所在行业的竞品进行调研,输出MRD,为产品的优化提供相关信息;
    3、掌握一定的需求分析方法论,能够有效输出需求规格书/需求分析报告,协助需求经理工作;
    4、持续跟进项目上线后的运营数据反馈,从数据中不断优化业务需求,让客户更满意;
    5、有较强的文字功底,能独立完成产品设计文档的编写
    具体要求:
    1、本科及以上学历,专业不限,1-2年工作经验;
    2、参与过一个完整生命周期的项目经验;
    3、能够熟练使用AxureRP、墨刀、Visio、PS、PPT等工具,有PS经验者优先;
    4、有耐心及同理心、善于倾听,乐于沟通,逻辑分析能力强;
    5、有 to B产品工作经验、通信行业经验者优先;
    6、面试时,请准备一份您最满意的产品原型展示给我们。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:1人;持阿里云认证优先

    职位10: 测试主管

    Base地点: 北京
    岗位职责:
    1、负责部门所有项目的日常管理工作;
    2、负责测试管理体系的建立与执行;
    3、负责测试项目工作的全局安排,并解决测试工作中出现的问题,保证测试工作的顺利开展;
    4、协助开发部门进行系统级别优化并提供高效合理的解决方案、主导测试需求的提交并推动实现;
    5、评估测试方案、测试策略和相关测试报告;
    6、培养指导测试工程师,并组织相关培训工作,保证测试团队能力的持续提高。
    具体要求:
    1、计算机、通信等相关专业本科及以上学历,3年以上测试管理经验
    2、熟悉主流的测试方法与理论,掌握主流的测试及管理工具;
    3、熟悉通信领域综合解决方案测试的优先;
    4、精通硬件测试过程、性能测试、可靠性测试;
    5、精通产品测试及解决方案测试;
    6、具有良好的沟通能力,较强的分析、解决问题能力;
    7、具备良好的管理、组织和协调能力,有高度的责任心、敬业精神和踏实严谨的工作作风。
    简历投递方式:
    1.简历通过邮件同时发送至stephanie.zhangx@alibaba-inc.com和wangzk@tydic.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:1人;持阿里云认证优先
    ]]>
    【外企德科浙江(FESCO Adecco Zhejiang)】- 招聘职位详情(社招) Fri, 20 Jun 2025 02:20:33 +0800 外企德科浙江(FESCO Adecco Zhejiang)介绍

    外企德科浙江(FESCO Adecco Zhejiang)
    2010年,FESCO与Adecco宣布携手成立FESCO Adecco,这一中国500强与世界500强的联手,开启了一个新的发展黄金期。FESCO Adecco致力于成为最专业的人力资源全面解决方案提供商,成为客户最值得信赖的人力资源合作伙伴。为客户打开通向国际市场及创新型人力资源服务模式的大门,推动中国乃至全球的人力资源外包产业的健康发展。
    北京外企服务集团(FESCO)
    FESCO 成立于1979年,是开创中国人力资源服务行业的第一家企业,是中国人力资源业界最具竞争力和品牌价值的企业,是中国500强企业之一。FESCO服务于来自上百个国家和地区的3.5万家客户,以及在这些机构中工作的350余万名中外雇员。在全国建立了230余家投资公司及分公司,形成了以北京和上海为中心、覆盖全国31个省市自治区400余座城市的服务网络,通过“一地签约,全国服务”为客户提供便捷。2010年起,FESCO与德科集团(The Adecco Group)成立了6家合资公司,使FESCO国际化的人力资源服务网络遍布全球。
    德科集团(The Adecco Group)
    德科集团(The Adecco Group)是全球人力资源服务行业的领航者,服务客户超过100,000家,每天为超过100万名求职者提供有意义的职业机会。在全球60多个国家和地区拥有超过34,000名全职员工,通过人才和科技引领人力资源行业的变革。凭借旗下的全球品牌Adecco,Modis,Badenoch & Clark,Spring Professional,Lee Hecht Harrison,Pontoon,Adia,YOSS,GENERAL ASSEMBLY和VETTERY,提供涵盖短期招聘、长期招聘、职业过渡、人才开发和人力资源外包等全面的人力资源解决方案。德科集团总部位于瑞士苏黎世,是《财富》世界500强人力资源服务供应商,在瑞士苏黎世(SIX Swiss Exchange)证券交易所公开上市。2018,获评卓越职场®研究所(Great Place to Work® Institute)发布的“世界最佳工作场所”榜单第5名。

    职位1: 云计算电话销售

    Base地点: 杭州
    岗位职责:
    1)云计算电销岗位,基于互联网云化技术理念为企业客户提供咨询及业务架构规划;负责阿里云产品及解决方案销售技术支持;
    2)负责对公司提供的客户资源进行业绩转化,对客户业绩产出负责;
    3)需要对云计算事业充满使命感,坚信云计算对行业的变革、对个人职业生涯的改变
    4)需要一定的商务推进能力,包括对中小企业正常商务采购决策流程,招投标流程的熟悉
    5)用自己的专业、信誉、口碑、智慧为客户创造价值
    具体要求:
    1.2年及以上IT相关行业背景,熟悉云计算基础产品(包括:弹性计算,数据库,存储,网络,安全等)优先;
    2.有一定的销售经验,参加过IT项目招投标或者项目交付优先
    3.对于云计算在特定行业中的应用和发展趋势有一定了解,了解电商/在线教育/游戏/音视频/物联网等等行业基本情况者优先
    简历投递方式:
    1.简历通过邮件发送至stephanie.zhangx@alibaba-inc.com
    2.邮件命名方式:姓名-投递公司-投递岗位

    *招聘人数:10-15人;持阿里云认证优先
    ]]>
    阿里云新品发布会周刊第63期 丨 优秀技术人,如何做到高效沟通? Fri, 20 Jun 2025 02:20:33 +0800 点击订阅新品发布会

    新产品、新版本、新技术、新功能、价格调整,评论在下方,下期更新!关注更多新品发布会!


    精品直播

    第102期:新一代云原生数据仓库AnalyticDB 全新升级

    直播时间:2020年7月23日 14:00-15:00 查看直播

    TB1odtaOXT7gK0jSZFpXXaTkpXa-720-150.jpg

    云原生数据仓库AnalyticDB MySQL版(简称ADB,原分析型数据库MySQL版)是一种支持高并发低延时查询的新一代云原生数据仓库,全面兼容MySQL协议以及SQL:2003 语法标准,可以对海量数据进行即时的多维分析透视和业务探索,快速构建企业云上数据仓库。产品规格按需可选,基础版成本最低,适合BI查询应用;集群版提供高并发数据实时写入和查询能力,适用于高性能应用;弹性模式版本存储廉价按量计费,适用于10TB以上数据上云场景。

    讲师介绍:

    议题1:数字经济时代的云原生数据仓库演进趋势及进展解读

    李飞飞(飞刀)- 达摩院数据库首席科学家、阿里云智能数据库事业部总负责人

    议题2:迎接开放化、在线化、云原生数仓新时代

    占超群(离哲) - 阿里云智能数据库产品事业部OLAP产品部

    议题3:聚水潭:云原生数据仓库助力企业实现商业洞察

    赵坚密 聚水潭 - BI专家

    议题4:ADB实时数仓在基金业务场景的应用

    王哲 - 天弘基金首席架构师

    点击了解产品详情

    热门阅读

    1、秒懂云通信:如何用阿里云平台发短信?

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    手把手教你如何用阿里云平台发短信,超详细控制台步骤解析,快速上手!更有1650元短信体验代金券和免费试用,点击速抢! 查看原文

    2、初次使用 Elasticsearch 遇多种分词难题?那是你没掌握这些原理

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

    初次接触 Elasticsearch 的同学经常会遇到分词相关的难题,比如如下这些场景:

    1、为什么命名有包含搜索关键词的文档,但结果里面就没有相关文档呢?
    2、我存进去的文档到底被分成哪些词(term)了?
    3、我得自定义分词规则,但感觉好麻烦呢,无从下手

    如果你遇到过类似的问题,希望本文可以解决你的疑惑。 查看原文

    3、全国交通智慧升级,阿里云视频上云打造高速公路“视觉中枢”

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg 在交通行 智慧高速的未来是联网联控,视频是核心抓手。阿里云高速公路视频上云解决方案聚焦全国高速“一张网”,以 “视频联网”构建高速公路“视觉中枢”,实现交通部提出的可视、可测、可控、可服务的目标。
    可视:利用阿里云遍布全国的边缘节点以及高性能的视频接入网关,为高速公路提供就近的视频上云服务。
    可测:依托领先的CDN网络加速能力,打造高速公路视频一站式接入、多场景智能分析、稳定流畅的应用创新平台。
    可控:内置达摩院人工智能算法引擎,30+种视觉场景式分析,算法自主学习。
    可服务:基于交通智能引擎,构建态势感知、事件处置、公众服务、养护施工等智能应用,提升出行便利度和幸福感。 查看原文

    4、优秀技术人,如何做到高效沟通?

    9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg 本文讲了很多的不同沟通场景中的技巧,这些沟通技巧有的时候换个角度也可以看成沟通中的套路。在这里,我需要强调一下,一个沟通的成败,套路的东西会有效果,但是效果也是有限的,沟通中更重要的还是要看沟通的动机。你沟通的目的是不是希望要实现 “1+1>2” 的效果,你是不是有一个开放的心态,是不是重视差异化、视每次冲突是成长的机会。只要是有一个真诚的心,就算沟通技巧上问题重重,我相信别人还是愿意和你合作。这就是所谓的:“多一点真诚,少一点套路!” 查看原文

    往期回顾

    第101期:2020阿里云弹性计算年度发布 活动页面 直播视频

    数字经济蓬勃发展,企业上云已成浪潮;云计算时代,企业需要从流量端到IT架构做更精细的优化;疫情突袭,降本增效成为各个企业的首要命题。
    阿里云弹性计算十年,与客户一同进化,重构计算服务,帮助客户应对挑战,业务创新。

    第100期:mPaaS 小程序新品发布 活动页面 直播视频

    源自于支付宝小程序框架,亿级线上业务体量的锤炼,安全性媲美支付宝原生能力。不仅面向自有 App 投放小程序,更可快速构建打包,覆盖支付宝、淘宝、钉钉等应用。

    第99期:阿里云微服务引擎MSE2.0重磅升级发布 活动页面 直播视频

    注册和配置中心是微服务架构中的重要组件,往往采用 ZK/Nacos/Eureka 等开源方案进行自建,但因其依赖复杂,往往给客户带来的较高的建设和运维成本,同时,在 Hbase、Spark或 Kafka 等大数据的环境下,会依赖 ZK 进行分布式系统的协调,此时,基于微服务引擎 MSE 提供的托管服务,可以极大的降低运维复杂度,并提高可用性。

    第98期:云数据库Redis 6.0版重磅首发 活动页面 直播视频

    Redis 作为稳居世界排名第一的 KV 内存数据库,同时也是最受欢迎的开源缓存产品,是应对高并发,大流量,低延迟业务场景的不二选择。6.0版本为Redis史上最重量级的一次发布,距离社区版 GA 仅不到一周时间,阿里云全球首发最新版 Redis 6.0云数据库,全面涵盖开源Redis 6.0,包括RESP3新协议的支持,ACL 管理,多 IO 线程,SSL 加密,客户端缓存,集群代理等多项重大更新。在本次发布会中,您还将了解到:最新的Redis产品技术解读、前瞻性的Redis社区发展方向解析等。

    第97期:专属钉钉解决方案全新上线 活动页面 直播视频

    钉钉的用户数量已经突破3亿,客户对钉钉提供企业版的诉求越来越强。针对企业级共性需求,客户可按需选择专属设计、专属存储、专属安全和最高级别的专属App,定制出千人千面的钉钉,助力企业打造自己的钉钉。

    第96期:企业出海全球化网络新品发布会 活动页面 直播视频

    中国企业全球化正当时,在国内人口红利见顶,国家政策支持的双背景之下,“全球化”被越来越多的企业视为发展新赛道。出海浪潮下,企业如何利用云服务快速占据先机?本议题将为您分享阿里云对出海战略的洞察,详解阿里云网络服务如何全力支持各领域海外业务拓展。

    第95期:阿里云EDAS 3.0版重磅升级发布会 活动页面 直播视频

    EDAS3.0在微服务治理、K8s集群纳管和监管控一体化、生态集成等基础能力上进行了全面的升级。本次发布会,EDAS资深技术专家和资深产品专家将重磅发布EDAS3.0版本,同时还邀请了安利和云途时代分享EDAS业务实践,讲述数字化转型实战故事。

    第94期:阿里云政企安全加速解决方案全新发布 活动页面 直播视频

    阿里云政企安全加速解决方案是依托阿里云全球分布的加速节点、领先的安全防护及等保合规等能力,打造的边缘安全和加速一站式服务,解决政府、金融、传媒、传统企业内容分发安全和加速性能的问题,为云上业务保驾护航!

    第93期:机器学习PAI DSW2.0 & Alink商业版新品发布会 活动页面 直播视频

    PAI起初是一个定位于服务阿里集团的机器学习平台,致力于让AI技术更加高效、简洁、标准的被公司内部开发者使用。对集团内,PAI服务了淘宝、支付宝、高德等部门的业务。随着PAI的算法的不断积累,2015年底PAI作为天池大赛的官方比赛平台在阿里云正式上线,也成为了国内最早的云端机器学习平台之一。随着PAI在阿里云的业务的不断发展,2018年PAI平台正式商业化,目前已经在公有云积累了数万的企业客户以及个人开发者,是目前国内领先的云端机器学习平台之一。

    第92期:云服务器ECS内存增强型实例re6全新发布 活动页面 直播视频

    实例是能够为您的业务提供计算服务的最小单位,不同的实例规格可以提供的计算能力不同。本章节为您介绍在售的所有ECS实例规格族,包括每种实例规格族的特点、在售规格和适用场景。根据业务场景和使用场景,ECS实例可以分为多种实例规格族。根据CPU、内存等配置,一种实例规格族又分为多种实例规格。ECS实例规格定义了实例的基本属性:CPU和内存(包括CPU型号、主频等)。但是,ECS实例只有同时配合块存储、镜像和网络类型,才能唯一确定一台实例的具体服务形态。

    第91期:5大安全产品全面升级在线发布 活动页面 直播视频

    IT基础设施云化和业务全面上云,企业在享受数字红利的同时,安全也变得更为重要。阿里云5款产品全面升级,从混合云安全、容器安全、身份管理等多个主流安全维度全面提升产品能力,让企业能够快速利用云原生安全优势,提升自身安全体系建设,同时也能快速应对疫情多变性,满足数字化发展长远安全需求。

    资料下载

    开放下载!新品发布产品资料

    最新产品发布资料大合集 ,以及你想了解的讲师PPT都可点击下载!

    产品动态

    新功能/版本:

    1、 日志服务 - 日志服务打通阿里云操作审计日志,支持接入ECS、RDS、OSS的平台操作日志 查看产品 产品文档

    行业领先的日志大数据解决方案,一站式提供数据收集、清洗、分析、可视化和告警功能。全面提升海量日志处理能力,实时挖掘数据价值,智能助力研发/运维/运营/安全等场景。

    2、 大数据计算服务 - MaxCompute使用包年包月计算资源作业支持优先级功能 查看产品 产品文档

    MaxCompute(原ODPS)是一项大数据计算服务,它能提供快速、完全托管的PB级数据仓库解决方案,使您可以经济并高效的分析处理海量数据。欢迎加入钉钉交流群11782920。
    MaxCompute — 持续定义SaaS模式云数据仓库 >>

    3、 终端访问控制系统 - 阿里云安全产品 - 终端访问控制系统 - 应用加固发布 查看产品 产品文档

    移动开发平台(mPaaS)是源于支付宝的移动开发平台,为移动开发、测试、运营及运维提供云到端的一站式解决方案,能有效降低技术门槛、减少研发成本、提升开发效率,协助企业快速搭建稳定高质量的移动应用。

    扫描二维码加入社区圈

    阿里云新品发布会交流群:群号(30675445)点击加入

    5555

    ]]>
    该不该在 Rust 上做点投资? Fri, 20 Jun 2025 02:20:33 +0800 作者 | 雷卷
    来源 | 凌云时刻(微信号:linuxpk)


    前言

    Rust 在 Stack Overflow 上连续5年被评为最想学习的编程语言,一直备受好评,所以这篇文章完全就是标题党,当然必须投资,明眼人都知道,也不需要我来为Rust摇旗呐喊。

    image.png

    下面内容是我个人一些学习点滴,同时结合我平时工作的一些情况,探讨一下 Rust 的可行性,算是落地场景讨论吧。

    当然你可以理解为一个Up主做直播,没有什么话题,就是和大家随便聊聊技术,不过我这个是文字版的。


    Rust 和云

    微软的这篇文章 Microsoft: Rust Is the Industry's 'Best Chance' at Safe Systems Programming 阐述的非常清楚,云是工业级的产品,当然需要工业级的安全系统。这里的安全不是指互联网安全等,而是软件开发中的 Memory Safe、Type Safe 和 Thread Safe等,这样才能保证产品的运行其的稳定性。

    我们知道新产品发布后,接下来一段时间基本就是各种 bug 修复,这些 bug 通常不是业务上的 bug,而是代码级别的 bug,比如内存溢出啊、空指针、并发控制没有做好导致数据错误等等。这些 bug 都很难被测试出来,如果有一个好的开发语言能帮助消除这方面潜在的很多问题,是不是非常好?

    性能,这个大家不用担心,Rust is as fast as C and C++。

    我非常认同文章中的观点,Rust 是非常好的基础设施类云产品开发语言,这是稳定性的保障,也是对客户负责。大家都说用互联网的思维做云产品,但是云厂商的重要客户都是企业客户,稳定性保障是第一位的,可能让企业来用自己的业务来为云产品试错。

    另外一个消息:Programming languages: Now Rust project looks for a way into the Linux kernel, 也就是 Linux 内核可能拥抱 Rust 语言,当然 Linus Torvalds 是已经认可啦。

    image.png


    产品 Rust SDK 开发

    使用 Rust 开发基础设施云产品的优点就不赘述啦!性能好、稳定性高。

    如果我们做不到产品用 Rust 开发,那么是不是普通程序员就不能接触 Rust 啦?完全不是,这里有一个产品
    Rust SDK 开发。很多云厂商的中间件产品都是 Java 语言、Cpp 语言开发的,我们都需要开发不用开发语言的 SDK,如 Java SDK、Python SDK 等。

    那么产品的 Rust SDK 有什么不同之处吗?这里我给你解释一下。这里我们就举 Alibaba Dubbo 的例子吧,如果你不知道 Dubbo,现在只要理解为分布式 RPC 调用框架就可以。Dubbo Rust SDK 有以下一些特点:

    • 为Rust应用提供访问 Dubbo 的服务支持

    如果客户的系统本来就是采用 Rust 开发,那么通过Rust访问 Dubbo 服务,这个需求应该没有什么好反驳的吧?要知道 Rust Web 框架 Actix-web 在性能测试中是排名靠前的。当然对 Dubbo Rust SDK,尤其针对IO和网络这一块,基本上都是通过 Tokio 实现的,所以默认就是支持异步化的,性能当然也是非常高的。

    稍等一下,写一个涉及网络调用的 SDK 哪那么简单啊?

    你就说一下使用 Tokio,还是有非常多的工作量的,不是那么好写的。 理论上是这样的,但是如果你看一下 Tokio 0.2.22版本的 tutorial ,你会发现就非常简单啦!这个 tutorial 不是告诉你如何写一个 Echo Server & Client,而是告诉你如何实现一个 Mini Redis,文档和代码样例都非常全,你只要一些调整,估计就能初步实现产品 Rust SDK 的功能。

    image.png


    • 为 Rust WebAssembly 提供访问 Dubbo 支持

    我们知道 WebAssembly 当前首选语言是Rust,假设你想在 WebAssembly 中访问 Dubbo 服务,如在浏览器中,我们只需要做 WebSocket 适配,在 WebAssembly 独立运行环境,我们可以通过 WASI Socket 来访问 Dubbo 服务。而针对 Wasm 的 Rust SDK 基本就是共享 Rust SDK 代码,然后做一些修改。

    当然 WebAssembly 还承担着通用开发包(Universal Library)的角色,如果大家看过 wasmtime demo 的例子就应该能理解。


    • 为 Deno JavaScript / TypeScript 提供访问 Dubbo 支持

    这里我们不想讨论 Node.js 和 Deno 谁是最后大赢家的问题,假设一些 JavaScript / TypeScript 的开发工程师选择了 Deno,我们知道 Deno 是基于 Rust 构建的,通过 Deno Plugin 为 JS / TS 提供调用接口,然后通过对应产品的 Rust SDK 实现,这样你不需要做太多的工作,使用 Deno 平台的同学就可以通过 JS / TS 访问你发布的服务啦。

    Node.js 的开发者众多,但是我们都知道使用C++开发一个 Node.js 扩展不是那么容易的,只有极少数的同学有这个能力。那么开发一个 Deno Rust Plugin 复杂吗?可以说是非常简单,你只需要参考一下 Calcite 框架, 有非常好的文档和样例,同时结合一下 https://crates.io/ 上的开发包,开发一个高质量的 Deno Rust plugin 非常简单。


    • 为 C 语言提供访问 Dubbo 服务支持

    如何为 C 语言提供 Dubbo 的访问支持?如果你问这个问题,估计会被打死的,但是没有关系,假设一些 ARM 设备的 C 程序想访问 Dubbo 服务,我们完全可以通过 Rust FFI 导出 cdylib 库,提供给C语言调用,这样省去我们开发 C SDK 的成本,而且这个成本并不低。我之前还想给 RSocket 写一个 C 的 SDK,仔细想了一下还是放弃啦,现在打算通过 Rust SDK 来为 C 提供访问接口。如果 C 能访问,那么基于 C 之上的一些小众语言,如 Lua 等,是不是也是可以访问 Dubbo 服务的。


    • 为其他语言提供运行期支持

    上面我们谈到了 Rust SDK 通过 FFI 为 C 语言提供对应的 SDK,同样原理还可以套用到其他语言上,典型的就是 Calling Rust From Python,这个网上一堆案例,同样的还试用于 Ruby 语言。Java 有没有对应的案例呢?有的,就是 java-ext-wasm 这个项目,也就是在 JVM 中内嵌 WebAssembly 运行期环境,来运行 WebAssembly,而这个 WebAssembly Runtime 就是 Rust 编写的。


    Rust 的开发者体验

    Rust 的开发体验非常好,我们都知道 JavaScript 的开发者体验基本是 No.1 的,所以我们就不拿 Rust 和 Golang、Java 来对比,我们直接看一下 Rust 和 JavaScript 的开发体验对比,如下:

    image.png

    个人觉得 Rust 一些方面比 JavaScript 还好,如内置单元测试支持、example 样例支持、 文档 doc、代码格式化 format、Clippy(Linter) 等支持,这个是比 JS 要好一些的。

    如果你拿 C++ 和 Rust 做开发者体验对比,那简直是地下和天上,诸如 Linux、Windows 和 Mac 平台的一致性等,这些都是 C++ 无法 Rust 对比的,更不用说 Rust 还有 https://crates.io/ ,这个也是 C++ 没有的。

    目前来说,Rust 一个不好的开发者体验就是编译速度太慢,没有办法,编译器做了非常多的工作,但是好像有一个 sccache - Shared Compilation Cache 能提升编译速度,这个我还没有使用过,可能是我的项目都不够大,我还没有遇到编译速度非常慢的情况,基本还能接受。 当然 Rust 社区也在讨论 pre-built 依赖问题,可能需要一些时间。


    Rust 没有想象的那么复杂 学习成本没有那么高

    讲到这里,一定有同学想尝试学习和了解Rust,那么学习成本高不高?个人觉得要从几个方面来看。

    • 学习资料

    在所有的开发语言中,Rust 的文档是最好的,没有之一。https://www.rust-lang.org/learn 上的《The Rust Programming Language》 可以说是最好的学习图书,完全免费,且更新比较快。个人觉得质量堪比 Pragmatic Bookshelf 的《Programming Ruby》,但是 Rust 是免费的,而且经常更新的。当然其他 Rust 相关的文档,如 Rust By Example,Rust WebAssembly 等,文档质量都非常高。

    另外大家都喜欢的 Cheat Sheet,你看一下 Rust 的 Cheat Sheet,就明白啦。 https://cheats.rs/

    当然其他语言也有对应的文档,但是我想问一下有几个 Java 开发者是去 Oracle Java 站点学习 Java 的?但是 Rust 做的就不一样,所有的学习文档都堪称一流,让你学习无压力。


    • Rust Module 理解起来不容易

    确实刚开始一头雾水,我觉得这篇文章 Clear explanation of Rust’s module system 非常好,废话多说无益,看完后基本全明白啦!

    image.png


    • Rust Ownership

    刚接触的同学非常头痛,至少我是,但是这也是 Rust 的特点,没有 GC,但是能做到 GC 差不多的效果,当然要走不同寻常路。但是也没有那么头痛,找几篇 Rust Ownership 的文章看一下,就可以啦。另外我推荐一下这个站点,这个老头的 Rust 教学视频非常好,非常容易理解, 资料也比较多,相信你看啦就明白啦,你只需要一个好的指导。

    image.png


    • Rust Enum

    Rust 的 Enum 功能非常强大,这个要特别留意一下,个人觉得 Swift 的 Enum 能和 Rust Enum 有一拼。我们经常看到其他语言中的 Result、Optional 等,在 Rust 中都是通过 Enum 实现的。


    • Tokio

    Rust 其他的特性我就不说啦,其他语言都有。这里说明一下 Tokio。

    Tokio 是 Rust 下的异步化框架,其地位相当于 Netty 在 Java 的作用,知名的 Deno 框架就是基于 V8 + Rust + Tokio 构建的。如果你有 JavaScript 的经验同时了解 Promise,那么 Tokio 的 Future async / await 和 JavaScript 的 Promise async / await 差不多,Tokio 的 Asynchronous stream 和 JavaScript 的 AsyncIterableIterator 也类似,学习的成本并不高。如果是 Java 程序员,可能理解起来有些吃力,但是如果你了解 Java 下的 Reactive 框架,如 RxJava 和 Reactor,那么理解起来也比较容易。


    总结

    当然这些是个人学习、编写一些 Rust Demo 和 Deno 了解等得出的结论,没有什么大道理,就是落实在一些点上,当然也就不可能非常全面,也就提供给你参考一下。

    最后,学习一门语言,千万不要听什么语言大师的,就是自己看资料、写 Demo、同时多看一些知名开源软件的代码,然后自己得出结论,哪怕是那种从入门到放弃的那种。

    另外大家都可以参考一下 Considering Rust,帮助你更好地了解 Rust。

    扫描下方二维码关注“凌云时刻”公众号回复“Rust”获取Considering Rust演讲文稿PDF

    image.png

    ]]>
    EDAS发布单工作原理及问题排查 Fri, 20 Jun 2025 02:20:33 +0800 引言

    用户在使用EDAS进行应用变更的时候,一定会接触到发布单功能。发布单以图形化的方式展现了应用部署、启动、停止和扩缩容等操作的具体过程,并能查看发布脚本的执行日志,以及出错信息。对于分批进行的变更,用户可以看到每一批变更的具体信息,对于手动分批的变更,还可以在发布单界面里进行“继续”操作。如果需要中途停止变更,可以使用“终止变更”功能强行中断正在进行的操作,从而减少因误操作而造成的损失。
    图1:EDAS发布单界面展示.png

    图1:EDAS发布单界面展示

    但发布单到底是如何工作的呢?为什么EDAS控制台上的操作可以影响ECS上的应用进程?这些发布单脚本到底是什么?如果发布单里出现了异常,有哪些排查的方法呢?本文将为您一一进行揭晓。

    发布单的工作原理

    图2:发布单工作原理图.png

    图2:发布单工作原理图

    (1)当用户提交应用变更请求以后,会先经过Gateway进行鉴权,然后传递给EAM服务。
    (2)EAM服务会将此变更请求生成为一条发布单记录,并存储在Redis缓存中。
    (3)AgentServer服务中运行有一个定时任务,会定时从Redis缓存中拉取发布单。
    (4)EAM服务将下发发布单任务到运行在ECS上的StarAgent服务。
    (5)发布单在ECS上执行并回传日志数据。
    这里用户可能会有疑问,为什么EAM服务将发布单放入Redis缓存之后,AgentServer拉取到的发布单任务又返还给EAM来进行下发,难道不是多此一举吗?这里其实Redis不仅仅是缓存,而是充当了一个消息队列。消息队列的能力包括异步处理和削峰填谷,当有大量发布单请求进入系统的时候,EAM会先将数据存储在Redis中,此时发布单提交操作就结束了。但是发布单的真正运行是由下游的AgentServer定时拉取后返还给EAM,从而实现了异步执行的效果。另外因为每个发布单的时间长短不一,但通常都是分钟级,这对于请求响应式的在线服务而言太过漫长,会导致线程池被占满,从而影响系统为后续请求提供服务。引入消息队列以后当有大量请求进入系统的时候会先缓存到队列中,再由系统慢慢消化处理,不至于让系统在高峰期失去服务能力。因此看上去多此一举的设计,在大规模分布式系统中起到了非常重要的作用。
    各组件之间的交互时序图如下:
    图3:各组件交互时序图.png

    图3:各组件交互时序图

    发布单的结构

    如图1所示,发布单模型具有一定的结构,从图中大致可以看出发布单有三个层级,如下图所示:
    图4:发布单层级图.png

    图4:发布单层级图

    一个发布单由若干条流水线组成,每条流水线又分为几个阶段,最后一个阶段下面包含一些任务,由此构成了发布单的结构模型。其中Event和Listener是发布单的通知机制,当一个任务结束以后,其通过事件报告自己的完成状态,再由其他组件通过Listener监听该事件进行一些业务逻辑上的后续处理。一个发布单的样例描述文件如下:
    图5:发布单样例描述文件.png

    图5:发布单样例描述文件

    可以看出该发布单有一条流水线(edas-app-deploy)和四个阶段(startPipeline、slbOffline、vipserverOfflineStage和deployApp),而每个阶段下又包含数量不等的任务,比如deployApp阶段包含hsfOffline、preStoreInstance和stopInstance等三个任务。阶段和任务都包含一些描述属性,例如任务之间是串行还是并行执行,每个任务的失败重试次数,以及是否忽略错误等等。

    发布单问题排查

    图6:发布单问题排查流程.png

    图6:发布单问题排查流程

    • service类型的任务,主要是涉及外部服务的调用,service类型的任务有(括号中是任务对应的名称,在日志中展示的是括号中的名称):

    Teinge上线/下线(tengineOnline/tengineOffline)
    Tegine配置更新(updateTengineConfig)
    SLB上线/下线、SLB设置权重(slbOnline/slbOffline、reopenSLBInstance/muteSLBInstance)

    • agent类型任务,需要通过staragent把命令下发到用户ECS上执行,主要有(括号中是任务对应的名称,在日志中展示的是括号中的名称):

    下载/安装Tomcat(pullTomcatPack/)
    下载应用程序包(pullWar)
    应用实例启动/停止(startInstance/stopInstance)
    Teigine启动/停止(startTengineInstance/stopTengineInstance)
    下载镜像(pullImage)
    URL/端口健康检查(healthCheckWithURL/healthCheckWithPort)
    删除应用实例/Tegine实例数据(deleteInstanceData/deleteTengineInstanceData)
    HSF服务优雅下线(hsfOffline)
    更新Tomcat配置(updateTomcatConfig)
    更新命令执行所需的python脚本(updateTaskScript)

    基本排查步骤

    (1)当应用实例生命周期变更操作出现问题,首先通过变更列表,进入发布单详情,查看具体执行失败的任务日志,根据错误具体信息,来定位问题。
    (2)Agent类型任务,可以在用户ECS机器上查看/home/admin/edas/logs/tasks/{taskId},用taskId标示(这些日志可以在控制台发布单详情页,具体任务“查看日志”功能中可以看到),也可以查看ECS上的日志/home/admin/edas/script/logs/edas.action.trace;也可以通过查询数据库,查询SQL: select cmd from cmd_record where cid = ‘任务Id’,任务id可以在发布单详情页面具体任务执行日志的最上方查看。
    tip:命令执行手动执行时不要最后的-q、–async等,通常以bash开头。
    (3)如果命令在机器上执行没有失败,则综合分析agent-server、edas-admin、edas-console的日志,定位错误原因。
    (4)service类型的任务,例如Tegine上下线、Tegine状态更新、SLB上下线、SLB权重设置等任务,需要在EDAS服务组件上查看相关日志。

    • Tegine任务命令下发在EDAS-AGENT-SERVER/home/admin/edas/logs/changeorder.log,或者/home/admin/edas/logs/agent-server.log中查询,回调日志在edas-admin中/home/admin/edas/logs/admin.log或者/home/admin/edas/logs/tengine.log中查询,参考tegine流程管控流程来定位问题。
    • SLB日志在EDAS-AGENT-SERVER/home/admin/edas/logs/slb.log,或者/home/admin/edas/logs/agent-server.log中查询。
    • 其他服务类型的日志可以在/home/admin/edas/logs/agent-server.log,或者/home/admin/edas/logs/changeorder.log中查询。

    常见问题解决思路

    • 命令通道不通

    这个直接可以通过发布单详情页中任务执行的日志来定位问题:一般任务执行日志有“please check if service is available”字样,可以在机器上执行pkill -9 staragent && /home/staragent/bin/agent.sh start,然后让用户重置应用实例。

    • 普通应用用户ECS目录或文件权限为root,无法操作相关文件

    一般在任务执行日志,或者edas.action.trace中会有显示权限不够的信息,可以在机器上执行chown admin:admin {要修改的目录或文件},然后重试。

    • Tegine启动、停止失败

    大多是一些前置操作失败或者目录权限不正确造成的,通常可以通过重置解决。

    • exit XXX 常见错误原因

    exit 3: 磁盘满(du -h)
    exit 5: 域名解析失败(ping -c 1 $domain 测试)
    exit 8: 下载脚本或WAR包失败,通常为4XX/5XX(curl测试)

    • Tegine上下线、状态更新失败

    查看与tegine管控交互的日志,定位问题,多起是因为Tegine上线的操作失败,造成后续对Tegine的操作失败;可以通过重置解决。

    • SLB上下线失败、权重设置失败

    通过查询EDAS-AGENT-SERVER/home/admin/edas/logs/slb.log,或者/home/admin/edas/logs/agent-server.log日志定位。

    作者:唐睿

    阿里云智能基础产品团队产品专家

    阿里云智能中间件和容器团队产品专家,15年企业级分布式架构及业务研发和产品经验,目前主要负责阿里云中间件PaaS领域的产品设计、用户洞察及技术布道等工作。打造更好、更易用、更满足用户需要的阿里云产品。

    我们是阿里云智能全球技术服务-SRE团队,我们致力成为一个以技术为基础、面向服务、保障业务系统高可用的工程师团队;提供专业、体系化的SRE服务,帮助广大客户更好地使用云、基于云构建更加稳定可靠的业务系统,提升业务稳定性。我们期望能够分享更多帮助企业客户上云、用好云,让客户云上业务运行更加稳定可靠的技术,您可用钉钉扫描下方二维码,加入阿里云SRE技术学院钉钉圈子,和更多云上人交流关于云平台的那些事。
    阿里云SRE技术学院(钉钉圈子)-lyl.png

    ]]>
    搞事情?Spring Boot今天一口气发布三个版本 Fri, 20 Jun 2025 02:20:33 +0800

    学无止境?本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

    前言

    各位好,我是A哥(YourBatman)。今天是2020-07-25,上午我正从https://start.spring.io准备down一个工程下来的时候,打开页面发现默认选中的Spring Boot版本号是2.3.2

    并非我刻意的去找到这个变化,而是由于我昨天 down下来的工程使用的Spring Boot版本是2.3.1,印象还在,所以今天一下子就发现了差异。

    既然升级了(虽然是小版本号),那就去官方楼一眼呗。不看不知道,一看还真让发现些内容:Spring Boot在同一天(2020-07-25)一口气发布了三个版本,这是要搞事情啊?

    小贴士:本文所有时间若未做特殊说明,指的均是北京时间

    正文

    Spring Boot目前还处于Active活跃的分支共有三个:

    因此就在今天,同一天里Spring Boot对它的这三条线做了更新:

    • Spring Boot 2.3.1 -> Spring Boot 2.3.2
    • Spring Boot 2.2.8 -> Spring Boot 2.2.9
    • Spring Boot 2.1.15 -> Spring Boot 2.1.16


    此次发版,距离上次正式发版(不区分分支)已足足有44天之久。

    有哪些升级?

    参考github上的Release详情,三个分支有如下三方面的改进:

    1. ]]> 数据中台的前世今生 Fri, 20 Jun 2025 02:20:33 +0800 就像20年前的虚拟化,5年前的容器化,这两年的中台化似乎也是势不可挡。但凡跟互联网沾边的行业,金融、零售、通讯都开始规划各自的中台化战略。抛开行业属性比较强的业务中台,我们今天就单独来讲讲数据中台的发展过程。
      说到中台,就不得不提一家公司:芬兰的游戏厂商Supercell。2015年年中,马云带领阿里巴巴集团高管,拜访了位于芬兰赫尔辛基的移动游戏公司Supercell。这家成立于2010年,仅凭政府借贷的30万欧元起家,从芬兰30平米小隔间走出来的游戏公司在短短4年时间里凭借《海岛奇兵》《部落冲突》《卡通农场》这三款游戏获得了17亿美元的收入,因此惊动了马云。
      Supercell首先带给马云的是组织的变革。Supercell将组织按产品线分割,而非传统的职能分割,管理以自底向上的方式,其创始人埃卡·潘纳宁在接受采访时就表示希望成为全球最“弱”的CEO,所有决定由游戏团队来做,毕竟研发者才是最接近玩家的,他们比任何人都更清楚玩家的需求。扁平化的管理也使得开发流程更加高效,敏捷的不仅是开发团队,还包括整个组织。由此阿里开始了根据产品进行事业群编制
      1.png

          解决了组织架构,为满足企业在不同时期所面临的外部并发,技术架构也必须是弹性扩展的,为此Supercell在成立第二年(2011年)就完成了应用的微服务化与数据库层的切片。甚至来说Supercell的微服务更轻量级,所有的游戏都复用一套网关代理、主页模块、匹配模块和战斗模块。整个组织使用一套高可用的组件库,一种开发语言,一个团队即One Repo,One Language,One Team。这**比阿里的三One概念早了整整八年**。

      2.png
      说到这里,大家多少可以看出一些端倪:阿里的组织和技术沿革很大程度上参照了这家公司的历史,那这家公司又是怎么发展起来,并且发展到什么程度了呢?
      最早的时候(2010年),Supercell的游戏很单一,数据量也不大,联机系统可以满足一切的查询/报表请求,因此对于数据汇聚集中分析的需求并不大,处在数据库阶段
      2020-07-26_1-14-57.jpg
      随着数据量的增加,OLTP无法同时支持事务处理和资料调取的请求,OLAP的业务开始多了起来,尤其是需要对用户的一些行为做分析,这些行为事件发生在客户端也可能在服务端,需要有一个统一的整合平台即数仓进行统一归集分析,Supercell选择了AWS的S3做为存储,并通过QlikView做界面展示
      2020-07-26_1-31-14.jpg
      由此便引申出了中台能力发展的第二个阶段,数据仓库阶段,这一阶段为日后的数据中台建设提供了全域数据,即生产资料
      2020-07-26_1-27-39.jpg
      随着游戏种类以及实时计算需求的增加,2013年下半年,Supercell终于考虑添加流式管道(Streaming Pipeline)作为数据缓冲层,由于之前和AWS的良好合作,这次的流管产品多数团队也是投票给了Kinesis,相对于Kafka,它的运维更加简单,体验更好(天生与AWS管理界面集成),后续可以对接各种事件消费者,不论是实时查询面板,第三方集成,实时分析,还是通过统一的KV日志格式集成入库S3。
      3.png
      4.png
      这一阶段,仓库进化为数据湖/大数据平台,具备了计算能力和流式数据的处理能力,可以提供算法输出,从而更好地服务前端应用。
      2020-07-26_2-01-35.jpg
      同年在数据湖设计上,Supercell使用Azkaban批量工作流任务调度器将CSV数据加载到AWS的Vertica数仓中,数据科学家通过开发接口对历史数据进行分析,形成一些数据服务,对内或对外输出。
      5.png

      这个架构在之后几年里遇到了新的瓶颈。首先Vertica集群的负载达到的峰值,第二在ETL的时候查询会变得缓慢,第三数仓的扩容花费巨大,第四存储与计算耦合紧密,第五再大的宽表数据库都会有列的极限。注意到这些问题之后,Supercell致力于:
      • 限制Vertica的数据存储量;
      • 将存储与计算资源切割;
      • 将ETL流程与查询流程切割;
      • 维护单一来源的置信数据;
      • 利用云的便利性优化资源使用。

            到2018年,公司决定将Amazon S3作为单一置信来源,以Parquet列表的形式存放数据,将Amazon EMR用作ETL转存目标,原来的Vertica只用来存放计算结果(例如账号、统计数据和KPI)而不是原始数据。

        6.jpg

        这样一来,Azkaban还是控制ETL流程,只是这次加载到Amazon EMR托管集群平台,然后以多张Parquet列表的形式存放到Amazon S3中,因为公司已经对数据行数做了切片,使用列表存储更能提升存储效率。

        分布式的S3作为唯一置信源,消费使用与列表更加契合的Athena作为查询工具,数据科学家可以到计算平台EMR中查看实时数据并做分析,包装成实时数据产品,也可以通过界面工具到置信源S3或结果数仓Vertica中查看OLAP大数据并进行建模分析,生成数据产品。

      7.png
      在2018年完成改造之后,由于EMR的延展性,可以瞬间扩展出大量的数据集就很好地解决了列宽的问题,并且有效地将存储与计算分离(EMR负责计算,S3负责存储)。在ETL时可以通过计划任务扩建一个集群跑ETL,跑完之后释放资源。而且EMR的环境对于数据科学团队来说更加友好。
      2020-07-26_2-23-33.jpg
      至此,数据中台的生产要素已全部构建完成,包括了数据(生产资料)、算法(生产工具)和算力(生产者),形成了人工驱动,自动学习的服务模式,支持绝大多数业务的同时实现了企业自身数据价值的最大化。

      展望未来,数据分析能力的强大并不能解决业务对于实时性的要求,而从各大云厂商纷纷致力于自身消息队列产品的开发来看,距离纯事件驱动的分布式消息中台不会太远,传统的分布式HDFS存储以后只能作为消息中台的归档系统,而像ETL,结构化数据库会渐渐淡出历史舞台,至少在互联网,大数据类的行业会是这样。

      ]]>
      微服务分布式事务解决方案-springboot整合分布式seata1.3.0 Fri, 20 Jun 2025 02:20:33 +0800 概述

      Seat是蚂蚁金服和阿里巴巴联合推出的一个开源的分布式事务框架,在阿里云商用的叫做GTS。
      项目地址:https://github.com/longxiaonan/springcloud-demo

      官网:http://seata.io/zh-cn/index.html

      一个XID和三个概念:

      • Transaction ID (XID) : 全局唯一的事务ID
      • Transaction Coordinator (TC) : 事务协调器,维护全局事务的运行状态
      • Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务
      • Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接受TC的提交或者回滚操作

      事务分为全局事务和本地事务。

      全局事务通过XID(全局唯一事务id)来标识。分支通过分支id和资源id来标识,标签每个分支事务都标有XID来标识是属于哪个全局事务。

      事务流程如下:

      image.png

      1. TM向TC申请开启一个全局事务,全局事务创建成功并且生成一个全局唯一的XID;
      2. XID在微服务调用链路的上下文中传播;
      3. RM向TC注册分支事务,汇报事务资源准备状态,将其纳入XID对应全局事务的管辖;
      4. RM执行业务逻辑;
      5. TM结束分布式事务,事务一阶段结束,通知TC针对XID的全局提交或回滚决议;
      6. TC汇总事务信息,决定分布式事务是提交还是回滚;
      7. TC调度XID下管辖的RM的分支事务完成提交或者回滚请求,事务二阶段结束。

      TC,TM和RM对应到实际服务节点

      全局事务需要在服务调用方的service上开启,服务调用方就是TM,其他被调用方就是RM。

      image.png

      两段提交的详细过程

      一阶段加载:
      image.png

      二阶段提交
      image.png

      二阶段提交失败,则进行回滚

      image.png

      image.png

      下载seata

      下载地址:http://seata.io/zh-cn/blog/download.html

      本文采用的是 seata 1.3.0 (2020-07-14)

      和springCloudAlibaba版本支持说明:

      https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

      添加seata依赖(建议单选)

      • 依赖seata-all
      • 依赖seata-spring-boot-starter,支持yml、properties配置(.conf可删除),内部已依赖seata-all
      • 依赖spring-cloud-alibaba-seata,内部集成了seata,并实现了xid传递

      因为是微服务方式,故添加依赖:

      • spring-cloud-starter-alibaba-seata推荐依赖配置方式
                  <dependency>
                      <groupId>io.seata</groupId>
                      <artifactId>seata-spring-boot-starter</artifactId>
                      <version>最新版</version>
                  </dependency>
                  <dependency>
                      <groupId>com.alibaba.cloud</groupId>
                      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                      <version>2.2.1.RELEASE</version>
                      <exclusions>
                          <exclusion>
                              <groupId>io.seata</groupId>
                              <artifactId>seata-spring-boot-starter</artifactId>
                          </exclusion>
                      </exclusions>
                  </dependency>

      文件配置

      修改registry.type、config.type

      文件在下载的seata包下

      启动包: seata-->conf-->file.conf,修改store.mode="db或者redis"
      源码: 根目录-->seata-server-->resources-->file.conf,修改store.mode="db或者redis"

      启动包: seata-->conf-->file.conf,修改store.db或store.redis相关属性。
      源码: 根目录-->seata-server-->resources-->file.conf,修改store.db或store.redis相关属性。

      registry.conf

      registry {
        # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
        type = "file"
      
        nacos {
          application = "seata-server"
          serverAddr = "127.0.0.1:8848"
          group = "SEATA_GROUP"
          namespace = ""
          cluster = "default"
          username = ""
          password = ""
        }
        ...
        config {
        # file、nacos 、apollo、zk、consul、etcd3
        type = "file"
      
        nacos {
          serverAddr = "127.0.0.1:8848"
          namespace = ""
          group = "SEATA_GROUP"
          username = ""
          password = ""
        }
       }

      改成

      registry {
        # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
        type = "nacos"
      
        nacos {
          application = "seata-server"
          serverAddr = "106.12.11.240:8848"
          namespace = "public"
          cluster = "default"
          username = ""
          password = ""
        }
      ...
        config {
        # file、nacos 、apollo、zk、consul、etcd3
        type = "nacos"
      
        nacos {
          serverAddr = "106.12.11.240:8848"
          namespace = "public"
          group = "SEATA_GROUP"
          username = ""
          password = ""
        }
      }

      store.mode 和 对应的nacos和db连接配置

      file.conf

      注意数据源类型,比如springboot的默认数据源是hikari而不是druid

      store {
        ## store mode: file、db、redis
        mode = "file"
        ...
        db {
          ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
          datasource = "druid"
          ## mysql/oracle/postgresql/h2/oceanbase etc.
          dbType = "mysql"
          driverClassName = "com.mysql.jdbc.Driver"
          url = "jdbc:mysql://127.0.0.1:3306/seata"
          user = "mysql"
          password = "mysql"
        }
       ...
      }

      修改为:

      store {
        ## store mode: file、db、redis
        mode = "db"
        
        ...
      
        ## database store property
        db {
          ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
          datasource = "druid"
          ## mysql/oracle/postgresql/h2/oceanbase etc.
          dbType = "mysql"
          driverClassName = "com.mysql.jdbc.Driver"
          url = "jdbc:mysql://127.0.0.1:3306/seata"
          user = "root"
          password = "123456"
        }
        ...
      }

      配置nacos-config.txt

      文件地址:https://github.com/seata/seata/blob/1.3.0/script/config-center/config.txt

      官网seata-server-0.9.0的conf目录下有该文件,官网seata-server-0.9.0的conf目录下有该文件,后面的版本无该文件需要手动下载执行。

      修改为自己的服务组名,各个微服务之间使用相同的服务组名,务必保持一致!

      service.vgroupMapping.my_test_tx_group=default
      service.vgroupMapping.my_test_tx_group1=default
      service.vgroupMapping.my_test_tx_group2=default
      service.vgroupMapping.my_test_tx_group3=default

      配置seata服务器mysql链接信息,注意数据源类型,比如springboot的默认数据源是hikari而不是druid

      store.mode=db
      ...
      store.db.datasource=druid
      store.db.dbType=mysql
      store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
      store.db.user=root
      store.db.password=123456

      执行nacos-config.sh脚本

      脚本地址:https://github.com/seata/seata/blob/1.3.0/script/config-center/nacos/nacos-config.sh

      官网seata-server-0.9.0的conf目录下有该文件,后面的版本无该文件需要手动下载执行。

      如果本地是windows,使用git工具git bash执行nacos-config.sh脚本:

      sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -u nacos -w nacos

      执行完成后nacos会新增seata配置。

      需要注意config.txt中目录的对应关系,否则可能提示finished,其实未执行成功!

      $ sh nacos-config.sh -h 106.12.11.240 -p 8848
      set nacosAddr=106.12.11.240:8848
      set group=SEATA_GROUP
      cat: /d/soft/config.txt: No such file or directory
      =========================================================================
       Complete initialization parameters,  total-count:0 ,  failure-count:0
      =========================================================================
       Init nacos config finished, please start seata-server.

      Seata Server需要依赖的表

      表的地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

      新建数据库seata, 创建如下三个表,用于seata服务, 0.0.9版本才有这个文件1.0.0版本后需要手动添加。

      -- the table to store GlobalSession data
      DROP TABLE IF EXISTS `global_table`;
      CREATE TABLE `global_table` (
        `xid` VARCHAR(128)  NOT NULL,
        `transaction_id` BIGINT,
        `status` TINYINT NOT NULL,
        `application_id` VARCHAR(32),
        `transaction_service_group` VARCHAR(32),
        `transaction_name` VARCHAR(128),
        `timeout` INT,
        `begin_time` BIGINT,
        `application_data` VARCHAR(2000),
        `gmt_create` DATETIME,
        `gmt_modified` DATETIME,
        PRIMARY KEY (`xid`),
        KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
        KEY `idx_transaction_id` (`transaction_id`)
      );
      
      -- the table to store BranchSession data
      DROP TABLE IF EXISTS `branch_table`;
      CREATE TABLE `branch_table` (
        `branch_id` BIGINT NOT NULL,
        `xid` VARCHAR(128) NOT NULL,
        `transaction_id` BIGINT ,
        `resource_group_id` VARCHAR(32),
        `resource_id` VARCHAR(256) ,
        `lock_key` VARCHAR(128) ,
        `branch_type` VARCHAR(8) ,
        `status` TINYINT,
        `client_id` VARCHAR(64),
        `application_data` VARCHAR(2000),
        `gmt_create` DATETIME,
        `gmt_modified` DATETIME,
        PRIMARY KEY (`branch_id`),
        KEY `idx_xid` (`xid`)
      );
      
      -- the table to store lock data
      DROP TABLE IF EXISTS `lock_table`;
      CREATE TABLE `lock_table` (
        `row_key` VARCHAR(128) NOT NULL,
        `xid` VARCHAR(96),
        `transaction_id` LONG ,
        `branch_id` LONG,
        `resource_id` VARCHAR(256) ,
        `table_name` VARCHAR(32) ,
        `pk` VARCHAR(36) ,
        `gmt_create` DATETIME ,
        `gmt_modified` DATETIME,
        PRIMARY KEY(`row_key`)
      );
      

      AT模式下每个业务数据库需要创建undo_log表,用于seata记录分支的回滚信息

      表的地址:https://github.com/seata/seata/blob/1.3.0/script/client/at/db/mysql.sql

      -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
      CREATE TABLE `undo_log` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `branch_id` bigint(20) NOT NULL,
        `xid` varchar(100) NOT NULL,
        `context` varchar(128) NOT NULL,
        `rollback_info` longblob NOT NULL,
        `log_status` int(11) NOT NULL,
        `log_created` datetime NOT NULL,
        `log_modified` datetime NOT NULL,
        `ext` varchar(100) DEFAULT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

      运行 Seata-server

      Linux/Unix/Mac

      sh seata-server.sh -p $LISTEN_PORT -m $STORE_MODE -h $IP(此参数可选)

      Windows

      cmd seata-server.bat -p $LISTEN_PORT -m $STORE_MODE -h $IP(此参数可选)

      $LISTEN_PORT: Seata-Server 服务端口
      $STORE_MODE: 事务操作记录存储模式:file、db
      $IP(可选参数): 用于多 IP 环境下指定 Seata-Server 注册服务的IP,配置自己的ip即可

      测试的时候 直接双击运行seata-server.bat 即可。

      # linux
      nohup ./seata-server.sh -h 192.168.1.4 -p 8092 &
      # windows cmd
      seata-server.bat -m file -h 192.168.1.4 -p 8092 

      用例

      参考官网中用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

      • 存储服务:扣除给定商品的存储数量。
      • 订单服务:根据购买请求创建订单。
      • 帐户服务:借记用户帐户的余额。

      项目地址:

      https://github.com/longxiaonan/springcloud-demo

      添加依赖:

      <!-- 分布式事务seata包 -->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                  <version>2.2.1.RELEASE</version>
                  <exclusions>
                      <exclusion>
                          <groupId>io.seata</groupId>
                          <artifactId>seata-all</artifactId>
                      </exclusion>
                      <exclusion>
                          <groupId>io.seata</groupId>
                          <artifactId>seata-spring-boot-starter</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <dependency>
                  <groupId>io.seata</groupId>
                  <artifactId>seata-spring-boot-starter</artifactId>
                  <version>1.3.0</version>
              </dependency>

      配置seata:

      seata:
        enabled: true
        application-id: ${spring.application.name}
        txServiceGroup: my_test_tx_group
        # 是否开启数据源自动代理 如果不开启设置为false
        enable-auto-data-source-proxy: true
        registry:
          type: nacos
          nacos:
            application: seata-server
            server-addr: 106.12.11.240:8848
            namespace:
        #      userName: "nacos"
        #      password: "nacos"
        config:
          type: nacos
          nacos:
            namespace:
            serverAddr: 106.12.11.240:8848
            group: SEATA_GROUP
      #      userName: "nacos"
      #      password: "nacos"

      在代码中通过注解开启:

      @GlobalTransactional(rollbackFor = Exception.class)

      请求逻辑

      image.png

      commint测试接口:http://127.0.0.1:8008/purchase/commit接口是可以正常执行完成的方法

      rollback测试接口:http://127.0.0.1:8008/purchase/rollback接口是会发生异常并正常回滚的方法

      测试

      当未开启seata服务rollback测试:

      未开启seata服务,启动业务服务节点, 业务节点的console log如下提示:

      2020-07-25 10:59:50.492 ERROR 11284 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager : no available service 'default' found, please make sure registry config correct
      2020-07-25 10:59:50.654 ERROR 11284 --- [eoutChecker_2_1] i.s.c.r.netty.NettyClientChannelManager : no available service 'default' found, please make sure registry config correct

      此时,访问rollback测试接口,controller不能进入加了@GlobalTransitional的service,无任何服务被执行和调用。console log如下报错:

      io.seata.common.exception.FrameworkException: No available service
           at io.seata.core.rpc.netty.AbstractNettyRemotingClient.loadBalance(AbstractNettyRemotingClient.java:257)
           at io.seata.core.rpc.netty.AbstractNettyRemotingClient.sendSyncRequest(AbstractNettyRemotingClient.java:133)
           at io.seata.tm.DefaultTransactionManager.syncCall(DefaultTransactionManager.java:95)
           at io.seata.tm.DefaultTransactionManager.begin(DefaultTransactionManager.java:53)
           at io.seata.tm.api.DefaultGlobalTransaction.begin(DefaultGlobalTransaction.java:104)
           at io.seata.tm.api.TransactionalTemplate.beginTransaction(TransactionalTemplate.java:175)

      开启seata服务rollback测试

      双击 `` 启动seata服务

      业务服务console提示如下log,说明注册成功

      2020-07-25 11:08:31.599  INFO 11284 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient    : register TM success. client version:1.3.0, server version:1.3.0,channel:[id: 0xdd694462, L:/169.254.249.134:3959 - R:/169.254.249.134:8091]
      2020-07-25 11:08:31.618  INFO 11284 --- [eoutChecker_2_1] i.s.c.rpc.netty.RmNettyRemotingClient    : register RM success. client version:1.3.0, server version:1.3.0,channel:[id: 0x5ece5f24, L:/169.254.249.134:3960 - R:/169.254.249.134:8091]
      2020-07-25 11:08:31.624  INFO 11284 --- [eoutChecker_2_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 264 ms, version:1.3.0,role:RMROLE,channel:[id: 0x5ece5f24, L:/169.254.249.134:3960 - R:/169.254.249.134:8091]
      2020-07-25 11:08:31.624  INFO 11284 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 263 ms, version:1.3.0,role:TMROLE,channel:[id: 0xdd694462, L:/169.254.249.134:3959 - R:/169.254.249.134:8091]
      

      rollback接口在执行到account服务的时候会抛异常:

      java.lang.RuntimeException: account branch exception
          at com.javasea.account.service.AccountService.debit(AccountService.java:34) ~[classes/:na]
          at com.javasea.account.service.AccountService$$FastClassBySpringCGLIB$$e3edd550.invoke(<generated>) ~[classes/:na]
          at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.5.RELEASE.jar:5.2.5.RELEASE]

      order服务作为account服务的调用方,会知道account服务发生了异常:

      feign.FeignException$InternalServerError: [500] during [GET] to [http://account-service/debit/1002/10] [AccountFeignClient#debit(String,Integer)]: [{"timestamp":"2020-07-25T03:36:18.494+0000","status":500,"error":"Internal Server Error","message":"account branch exception","path":"/debit/1002/10"}]
          at feign.FeignException.serverErrorStatus(FeignException.java:231) ~[feign-core-10.7.4.jar:na]
          at feign.FeignException.errorStatus(FeignException.java:180) ~[feign-core-10.7.4.jar:na]
          at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.7.4.jar:na]
          at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.7.4.jar:na]
          at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:156) ~[feign-core-10.7.4.jar:na]
          at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.7.4.jar:na]
          at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.7.4.jar:na]
          at com.sun.proxy.$Proxy89.debit(Unknown Source) ~[na:na]
          at com.javasea.order.service.OrderService.create(OrderService.java:38) ~[classes/:na]

      business服务log如下:

      2020-07-25 11:36:18.288  INFO 11284 --- [nio-8008-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [169.254.249.134:8091:30253423644925952]
      2020-07-25 11:36:18.289  INFO 11284 --- [nio-8008-exec-4] c.j.business.service.BusinessService     : 开始全局事务,XID = 169.254.249.134:8091:30253423644925952
      2020-07-25 11:36:18.627  INFO 11284 --- [nio-8008-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : [169.254.249.134:8091:30253423644925952] rollback status: Rollbacked
      feign.FeignException$InternalServerError: [500] during [GET] to [http://order-service/order/1002/2001/1] [OrderFeignClient#create(String,String,Integer)]: [{"timestamp":"2020-07-25T03:36:18.530+0000","status":500,"error":"Internal Server Error","message":"[500] during [GET] to [http://account-service/debit/1002/10] [AccountFeignClient#debit(String,Intege... (405 bytes)]
          at feign.FeignException.serverErrorStatus(FeignException.java:231)
          at feign.FeignException.errorStatus(FeignException.java:180)
          at feign.FeignException.errorStatus(FeignException.java:169)

      storage服务的扣减库存已经提交,因为全局事务没成功,故触发回滚:

      2020-07-25 11:36:18.303  INFO 5924 --- [io-8010-exec-10] c.j.storage.service.StorageService       : 开始分支事务,XID = 169.254.249.134:8091:30253423644925952
      2020-07-25 11:36:18.400  WARN 5924 --- [io-8010-exec-10] c.a.c.seata.web.SeataHandlerInterceptor  : xid in change during RPC from 169.254.249.134:8091:30253423644925952 to null
      2020-07-25 11:36:18.543  INFO 5924 --- [ch_RMROLE_1_7_8] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=169.254.249.134:8091:30253423644925952,branchId=30253423821086720,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_storage,applicationData=null
      2020-07-25 11:36:18.544  INFO 5924 --- [ch_RMROLE_1_7_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 169.254.249.134:8091:30253423644925952 30253423821086720 jdbc:mysql://localhost:3306/seata_storage
      2020-07-25 11:36:18.618  INFO 5924 --- [ch_RMROLE_1_7_8] i.s.r.d.undo.AbstractUndoLogManager      : xid 169.254.249.134:8091:30253423644925952 branch 30253423821086720, undo_log deleted with GlobalFinished
      2020-07-25 11:36:18.619  INFO 5924 --- [ch_RMROLE_1_7_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

      遇到的问题:

      console一直提示如下error log:

      00:46:38.070 ERROR 10652 --- [imeoutChecker_2] i.s.c.r.n.NettyClientChannelManager - no available service 'default' found, please make sure registry config correct
      00:46:47.729 ERROR 10652 --- [imeoutChecker_1] i.s.c.r.n.NettyClientChannelManager - no available service 'default' found, please make sure registry config correct

      一直百事不得其解。后面突然发现会不会是jar包问题。

      查看在父项目的pom配置:

      <!-- 分布式事务seata包 -->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                  <version>2.2.1.RELEASE</version>
                  <exclusions>
                      <exclusion>
                          <groupId>io.seata</groupId>
                          <artifactId>seata-all</artifactId>
                      </exclusion>
                      <exclusion>
                          <groupId>io.seata</groupId>
                          <artifactId>seata-spring-boot-starter</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <dependency>
                  <groupId>io.seata</groupId>
                  <artifactId>seata-spring-boot-starter</artifactId>
                  <version>1.3.0</version>
              </dependency>

      到本项目分析pom.xml的依赖关系,发现jia包竟然是1.1.0的,在父项目不是引入的1.3.0吗?

      image.png

      去文档中查找发现1.1.0是默认 spring-cloud-starter-alibaba-seata 2.2.1.RELEASE 内嵌的,也就是pom依赖关系基础问题。

      image.png

      先不管pom依赖的继承问题。将pom依赖分别添加到子项目即可。

      然后重启,RM注册成功:

      00:49:17.541 INFO  13260 --- [eoutChecker_1_1] i.s.c.rpc.netty.NettyPoolableFactory - NettyPool create channel to transactionRole:TMROLE,address:169.254.249.134:8091,msg:< RegisterTMRequest{applicationId='lmwy-flow-longxiaonan', transactionServiceGroup='my_test_tx_group'} >
      00:49:17.557 INFO  13260 --- [eoutChecker_1_1] i.s.c.r.netty.TmNettyRemotingClient - register TM success. client version:1.3.0, server version:1.3.0,channel:[id: 0x1d35034a, L:/169.254.249.134:9977 - R:/169.254.249.134:8091]
      00:49:17.557 INFO  13260 --- [eoutChecker_1_1] i.s.c.rpc.netty.NettyPoolableFactory - register success, cost 8 ms, version:1.3.0,role:TMROLE,channel:[id: 0x1d35034a, L:/169.254.249.134:9977 - R:/169.254.249.134:8091]
      00:49:32.077 INFO  13260 --- [.12.11.240_8848] c.a.n.c.config.impl.ClientWorker - get changedGroupKeys:[]
      00:49:38.880 INFO  13260 --- [.12.11.240_8848] c.a.n.c.config.impl.ClientWorker - get changedGroupKeys:[]
      00:50:01.684 INFO  13260 --- [.12.11.240_8848] c.a.n.c.config.impl.ClientWorker - get changedGroupKeys:[]

      本文项目地址:https://github.com/longxiaonan/springcloud-demo

      参考:

      http://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html

      https://sourcegraph.com/github.com/seata/seata-samples@master/-/blob/springcloud-nacos-seata/README.md

      ]]>
      学习 | Spring Cloud Config 从入门到精通 Fri, 20 Jun 2025 02:20:33 +0800 小小又开始学习了,这次学习的内容是Spring Cloud 相关内容,这次学习的是Config相关的内容。
      通过git完成分布式的配置文件的部署,达到更新git,就可以更新配置信息的功能、

      Server端

      添加相关maven

      这里配置Server端
      添加相关的依赖

       <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-config-server</artifactId>
              </dependency>

      更新相关配置文件

      server:
        port: 8080
      spring:
        application:
          name: spring-cloud-config-server
        cloud:
          config:
            server:
              git:
                uri: https://github.com/meteor1993/SpringCloudLearning # git仓库的地址
                search-paths: chapter6/springcloud-config  # git仓库地址下的相对地址,可以配置多个,用,分割。
                username: #Git仓库用户名
                password: #Git仓库密码

      其中,uri表明git的配置地址,search-paths表明git相关的配置路径,这里使用git作为分布式的配置文件的存储

      其配置文件的路径为

      SpringCloudLearning/chapter6/springcloud-config/springcloud-config-pro.properties
      访问地址为 https://github.com/meteor1993/SpringCloudLearning/blob/master/chapter6/springcloud-config/springcloud-config-pro.properties

      添加启动类相关的注解

      这里添加EnableConfigServer类相关的注解,

      用于激活Spring Cloud 对配置中心的相关激活

      package com.example.demo;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.config.server.EnableConfigServer;
      
      @SpringBootApplication
      @EnableConfigServer
      public class DemoApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
      
      }
      

      这个时候访问 http://localhost:8080/springcloud-config/pro 就可以实现对配置文件信息的访问
      其中springcloud-config为配置中心的文件名称。pro为对应的相关的配置文件。
      这里对应的相关的配置文件,命名有

      springcloud-config-dev.properties  对应于dev开发模式
      springcloud-config-pro.properties  对应于pro开发模式
      springcloud-config-test.properties  对应于test开发模式

      其访问的pro更改为相关的配置类型即可

      访问效果

      访问连接 http://localhost:8080/springcloud-config/pro 即可看到相关的配置信息

      用户端

      前面服务端已经访问完成,这里访问客户端

      新建子项目

      添加相关的maven

      <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-config -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-config</artifactId>
          <version>2.2.3.RELEASE</version>
      </dependency>

      添加配置文件

      这里配置文件分为两个配置文件,分别为application.yml 和 bootstrap.yml 这两个配置文件

      application.yml

      这里配置application.yml 配置文件

      server:
        port: 8081
      spring:
        application:
          name: spring-cloud-config-client

      再次配置bootstrap.yml 文件

      spring:
        cloud:
          config:
            name: springcloud-config
            profile: dev
            uri: http://localhost:8080/
            label: master
      

      这样就完成了一次的配置文件书写。
      其中name为配置git的name,profile对应于版本。url对应于server的信息,label对应于相关的分支。

      添加启动类

      package com.springcloud.configclient;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      public class ConfigClientApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(ConfigClientApplication.class, args);
          }
      
      }
      

      添加配置信息读取类

      添加配置信息的读取类

      package com.springcloud.configclient.controller;
      
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      /**
       * @Author: shiyao.wei
       * @Date: 2019/7/4 16:19
       * @Version: 1.0
       * @Desc:
       */
      @RestController
      public class HelloController {
      
          @Value("${springcloud.hello}")
          private String hello;
      
          @RequestMapping("/hello")
          public String from() {
              return this.hello;
          }
      }
      
      

      这样,就完成了配置信息的统一读取

      ]]>
      atomikos多数据源配置-在工作流(activiti)分库时的事务管理实战 Fri, 20 Jun 2025 02:20:33 +0800 配置多个数据源

      yml配置两个数据源, act和business:

      datasource:
          act:
            jdbcUrl: jdbc:mysql://localhost:3306/lmwy_product_act?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
            username: root
            password: 123456
            driverClassName: com.mysql.jdbc.Driver
          business:
            jdbc-url: jdbc:mysql://localhost:3306/lmwy_product?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver

      分别对应到两个数据源的配置:

      package com.zhirui.lmwy.flow.config;
      
      import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.boot.jdbc.DataSourceBuilder;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      
      import javax.sql.DataSource;
      
      
      /**
       *  多数据源配置类
       *      1.activiti数据库连接池
       *          默认, 我们也无法去修改源码故默认
       *      2.工作流业务数据库连接池
       *          明确指定 businessDataSource
       */
      @Configuration
      public class FlowDatasourceConfig extends AbstractProcessEngineAutoConfiguration {
          @Bean
          @Primary
          @ConfigurationProperties(prefix = "spring.datasource.act")
          @Qualifier("activitiDataSource")
          public DataSource activitiDataSource() {
              DataSource build = DataSourceBuilder.create().build();
              return build;
          }
      
          @Bean
          @ConfigurationProperties(prefix = "spring.datasource.business")
          @Qualifier("businessDataSource")
          public DataSource businessDataSource() {
              DataSource build = DataSourceBuilder.create().build();
              return build;
          }
      }
      

      业务的springdata配置和事物管理器配置

      package com.zhirui.lmwy.flow.config;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.boot.autoconfigure.domain.EntityScan;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.core.env.Environment;
      import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
      import org.springframework.orm.jpa.JpaTransactionManager;
      import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
      import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import javax.sql.DataSource;
      import java.util.HashMap;
      
      /**
       * 需要在配置类的上面加上@EnableJpaRepositories(basePackages={"dao层对应的包路径"}),这样jpa的dao层就注入进来了。结果启动spring boot 时发现,又有 Not a managed type: class ******的错误,经查询发现少了jpa entity路径的配置,在配置类的头部加上标记:@EntityScan("entity对应的包路径")
       */
      @Configuration
      @EnableJpaRepositories(
              basePackages = {"com.zhirui.lmwy.flow.dao"},//代理的dao接口所在的包
              entityManagerFactoryRef = "flowEntityManager",
              transactionManagerRef = "flowTransactionManager"
      )
      public class JpaRepositoriesConfig {
      
          @Autowired
          private Environment env;
      
          @Autowired
          @Qualifier("businessDataSource")
          private DataSource businessDataSource;
      
          /**
           * 创建entityManagerFactory工厂
           */
          @Bean
          @Primary
          public LocalContainerEntityManagerFactoryBean flowEntityManager() {
              LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
              em.setDataSource(businessDataSource);
              //配置扫描的实体类包 ,否则报错:No persistence units parsed from {classpath*:META-INF/persistence.xml}
              em.setPackagesToScan(new String[]{"com.zhirui.lmwy.flow.entity"});
              HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
              em.setJpaVendorAdapter(vendorAdapter);
              HashMap<String, Object> properties = new HashMap<>();
              // application.yaml配置文件的ddl-auto的值
      //      properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
              properties.put("hibernate.show_sql", "true");
              properties.put("hibernate.format_sql", "true");
              // application.yaml配置文件的database-platform的值
      //      properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
              properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
              properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
              em.setJpaPropertyMap(properties);
              return em;
          }
      
          /**
           * 创建事务管理器
           */
          @Primary
          @Bean
          public PlatformTransactionManager flowTransactionManager() {
              JpaTransactionManager transactionManager = new JpaTransactionManager();
              transactionManager.setEntityManagerFactory(flowEntityManager().getObject());
              return transactionManager;
          }
      }
      

      activiti中使用act数据源

      package com.zhirui.lmwy.flow.config;
      
      import lombok.AllArgsConstructor;
      import org.activiti.spring.SpringProcessEngineConfiguration;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.jdbc.datasource.DataSourceTransactionManager;
      import org.springframework.orm.jpa.JpaTransactionManager;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import javax.sql.DataSource;
      
      /**
       * Activiti 配置
       */
      @Configuration
      @AllArgsConstructor
      public class ActivitiConfig {
      
          private final DataSource dataSource;
      
          @Autowired
          @Qualifier("activitiDataSource")
          private DataSource activitiDataSource;
      
          @Bean
          public SpringProcessEngineConfiguration getProcessEngineConfiguration() {
              SpringProcessEngineConfiguration config =
                      new SpringProcessEngineConfiguration();
              config.setDataSource(activitiDataSource);
              config.setTransactionManager(activitiTransactionManager());
              return config;
          }
      
          @Bean
          public DataSourceTransactionManager activitiTransactionManager() {
              DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
              transactionManager.setDataSource(activitiDataSource);
              return transactionManager;
          }
      }
      

      事物管理问题

      service方法中只能一个生效,business的事务管理器是"flowTransactionManager",且配置了@Primary组件,所以下面的方法中使用的数据源就是"flowTransactionManager",那么针对数据源act的操作将无法回滚。

      @Override
      @Transactional(rollbackFor = Exception.class)
      public void commit(FlowTaskInstance flowTaskInstance, String tenantId) throws Exception {
          针对数据源act的操作
              针对数据源business的操作
              ...

      可以将@Transactional(rollbackFor = Exception.class) 指定事务管理器名称:

      @Transactional(rollbackFor = Exception.class, transactionManager = "activitiTransactionManager")

      那么方法上使用事物管理器为“activitiTransactionManager”, 但是针对数据源business的操作也将无法回滚。

      我们需要一种分布式事物管理器。

      * 如果是单应用单节点服务的多数据源事务,可以采用下午的 atomikos实现分布式市委 的方案。

      * 如果是微服务架构,那么直接在改基础上整合seata即可!

      atomikos实现分布式事务

      先了解下spring中事务的管理器

      PlatformTransactionManager顶级接口定义了最核心的事务管理方法,下面一层是AbstractPlatformTransactionManager抽象类,实现了PlatformTransactionManager接口的方法并定义了一些抽象方法,供子类拓展。最下面一层是2个经典事务管理器:

      1.DataSourceTransactionmanager: 即本地单资源事务管理器,也是spring默认的事务管理器。

      2.JtaTransactionManager: 即多资源事务管理器(又叫做分布式事务管理器),其实现了JTA规范,使用XA协议进行两阶段提交。

      3. atomikos是JTA规范的具体技术,比较火和流行。

      pom配置

      <!-- 分布式事务管理 参考:https://blog.csdn.net/qq_35387940/article/details/103474353 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jta-atomikos</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>

      yaml配置

      #datasource
      spring:
        jta:
          enabled: true
          atomikos:
            datasource:
              act:
                xa-properties.url: jdbc:mysql://localhost:3306/lmwy_product_act?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&pinGlobalTxToPhysicalConnection=true
                xa-properties.user: root
                xa-properties.password: 123456
                xa-data-source-class-name: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
                unique-resource-name: act
                max-pool-size: 10
                min-pool-size: 1
                max-lifetime: 10000
                borrow-connection-timeout: 10000
              business:
                xa-properties.url: jdbc:mysql://localhost:3306/lmwy_product?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&pinGlobalTxToPhysicalConnection=true
                xa-properties.user: root
                xa-properties.password: 123456
                xa-data-source-class-name: com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
                unique-resource-name: business
                max-pool-size: 10
                min-pool-size: 1
                max-lifetime: 10000
                borrow-connection-timeout: 10000

      配置多数据源和事务管理器

      package com.zhirui.lmwy.flow.config;
      
      import com.atomikos.icatch.jta.UserTransactionImp;
      import com.atomikos.icatch.jta.UserTransactionManager;
      import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.boot.jdbc.DataSourceBuilder;
      import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.transaction.jta.JtaTransactionManager;
      
      import javax.sql.DataSource;
      import javax.transaction.SystemException;
      import javax.transaction.UserTransaction;
      
      
      /**
       *  多数据源配置类
       *      1.activiti数据库连接池
       *          默认
       *      2.工作流业务数据库连接池
       *          明确指定 businessDataSource
       */
      @Configuration
      public class AtomikosConfig extends AbstractProcessEngineAutoConfiguration {
      
          @Primary
          @Bean(name = "actDatasource")
          @Qualifier("actDatasource")
          @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.act")
          public DataSource actDatasource() {
              return new AtomikosDataSourceBean();
          }
      
          @Bean(name = "businessDatasource")
          @Qualifier("businessDatasource")
          @ConfigurationProperties(prefix="spring.jta.atomikos.datasource.business")
          public DataSource businessDatasource() {
              return new AtomikosDataSourceBean();
          }
      
          @Bean("jtaTransactionManager")
          @Primary
          public JtaTransactionManager activitiTransactionManager() throws SystemException {
              UserTransactionManager userTransactionManager = new UserTransactionManager();
              UserTransaction userTransaction = new UserTransactionImp();
              return new JtaTransactionManager(userTransaction, userTransactionManager);
          }
      }
      

      业务jpa绑定数据源

      package com.zhirui.lmwy.flow.config;
      
      import org.hibernate.engine.transaction.jta.platform.internal.AtomikosJtaPlatform;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.boot.autoconfigure.domain.EntityScan;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.core.env.Environment;
      import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
      import org.springframework.orm.jpa.JpaTransactionManager;
      import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
      import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import javax.sql.DataSource;
      import javax.transaction.TransactionManager;
      import javax.transaction.UserTransaction;
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * 需要在配置类的上面加上@EnableJpaRepositories(basePackages={"dao层对应的包路径"}),这样jpa的dao层就注入进来了。结果启动spring boot 时发现,又有 Not a managed type: class ******的错误,经查询发现少了jpa entity路径的配置,在配置类的头部加上标记:@EntityScan("entity对应的包路径")
       */
      @Configuration
      @EnableJpaRepositories(
              basePackages = {"com.zhirui.lmwy.flow.dao"},//代理的dao接口所在的包
              entityManagerFactoryRef = "flowEntityManager",
              transactionManagerRef = "jtaTransactionManager" //指定jta的事务管理器
      )
      public class JpaRepositoriesConfig {
      
          @Autowired
          private Environment env;
      
          @Autowired
          @Qualifier("businessDatasource")
          private DataSource businessDataSource;
      
          /**
           * 创建entityManagerFactory工厂
           */
          @Bean
      //    @Primary
          public LocalContainerEntityManagerFactoryBean flowEntityManager() {
              // *** jta 事务管理 ***
      //        AtomikosJtaPlatform.setTransactionManager(transactionManager);
      //        AtomikosJtaPlatform.setUserTransaction(userTransaction);
      
              LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
      
              // *** jta datasource ***
              em.setJtaDataSource(businessDataSource);
      //        em.setDataSource(businessDataSource);
              // 配置扫描的实体类包 ,否则报错:No persistence units parsed from {classpath*:META-INF/persistence.xml}
              em.setPackagesToScan(new String[]{"com.zhirui.lmwy.flow.entity"});
              HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
              em.setJpaVendorAdapter(vendorAdapter);
              HashMap<String, Object> properties = new HashMap<>();
      
              // *** jta datasource ***
              properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
              properties.put("javax.persistence.transactionType", "JTA");
      
              // application.yaml配置文件的ddl-auto的值
      //      properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
              properties.put("hibernate.show_sql", "true");
              properties.put("hibernate.format_sql", "true");
              // application.yaml配置文件的database-platform的值
      //      properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
              properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
              properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
              em.setJpaPropertyMap(properties);
              return em;
          }
      
      }
      

      activiti绑定数据源

      package com.zhirui.lmwy.flow.config;
      
      import lombok.AllArgsConstructor;
      import org.activiti.spring.SpringProcessEngineConfiguration;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.transaction.jta.JtaTransactionManager;
      
      import javax.sql.DataSource;
      
      /**
       * Activiti 配置
       */
      @Configuration
      @AllArgsConstructor
      public class ActivitiConfig {
      
          @Autowired
          @Qualifier("actDatasource")
          private DataSource activitiDataSource;
      
          @Bean
          @Primary
          public SpringProcessEngineConfiguration getProcessEngineConfiguration(JtaTransactionManager jtaTransactionManager) {
              SpringProcessEngineConfiguration config =
                      new SpringProcessEngineConfiguration();
              config.setDataSource(activitiDataSource);
              config.setTransactionManager(jtaTransactionManager);
              return config;
          }
      
      }
      

      测试:

      执行任务提交方法,报错后都会进行回滚

      public void commit(){
          // 操作1:activiti执行任务,用的act数据源
          taskService.complete(task.getId());
          ...
          // 操作2:更新业务流程对象,保存业务对象,用的business数据源
          flowInstanceDao.save(flowInstance);
          ...
          int a = 100 / 0;
      }

      参考:

      http://www.manongjc.com/detail/6-anzuqtksygeselj.html

      https://www.cnblogs.com/xkzhangsanx/p/11218453.html

      https://www.jianshu.com/p/099c0850ba16

      ]]>
      读完《云原生架构白皮书》,我们来谈谈开放应用模型(OAM) Fri, 20 Jun 2025 02:20:33 +0800

      前言

      7月21日阿里云发布了《云原生架构白皮书》,该书由阿里云众多技术专家共同编撰而成,从云原生定义、技术、架构、产品、实践和发展趋势几个方面详细介绍了云原生这一近些年来大火的技术概念。受阿里云邀请,我有幸在该书发布前试读了该书,但是由于最近比较忙,现在才有空和大家分享我的试读感受。

      熟悉我的朋友肯定知道,去年开放应用模型(OAM)概念一经提出,我就十分关注这一技术模型,最近更是参与到了该模型的实现项目 Crossplane 中,同社区中的同学共同实现云原生技术“以应用为中心”这一终极愿景。但是苦于社区中的资料都是英文,同时自己的理解又比较片面,在向身边同事和其他不了解该项技术的同学科普 OAM 时,往往很难准确表达我的观点。

      OAM 是什么?OAM 能做什么?我们为什么需要 OAM?每每被同事进行灵魂拷问时,总是不能拿出完整、条理、有说服力的东西,只能根据自己的理解以及一些零零散散的技术文章来说明我的观点,很是不爽。但是当我读到《云原生架构白皮书》第三章中的开放应用模型(OAM)章节时,我知道我的问题解决了。该章系统的介绍了 OAM 这项技术的背景、定义、概念、实现和未来,读者只要对云原生稍有理解,就能轻松从这章中找到前面那些问题的答案。

      那么 OAM 到底是什么?

      从《云原生架构白皮书》的内容出发,结合我的理解,大致将 OAM 的特点分为以下三点:

      以应用为中心

      今年是 Kubernetes 项目诞生的第六年,在这六年中,以 Kubernetes 为首的云原生技术快速的改变着我们的技术架构,一个又一个的应用被拆分成微服务,打包成容器,运行在 Kubernetes 上。然而随着微服务越拆越多,管理微服务的难度也呈指数型增长,Kubernetes 中并没有”应用“这一概念,提供给我们的只有 deployment、StatefulSet 这样工作负载粒度的资源,而一个应用,可能由多个 Deployment、Service、以及各种相关配套资源组成(如:HPA 用于弹性伸缩、Ingress 用于外部访问等)。Kubernetes 并没有提供给我们一个统一的资源或者说是方法来管理这些相关资源,各个公司只能开发自己的 PASS 平台或设立规范约束自己的应用。

      OAM 的出现补充了“应用”这一概念,建立对应用和它所需的运维能力定义与描述的标准规范。换言之,OAM 既是标准“应用定义”同时也是帮助封装、组织和管理 Kubernetes 中各种“运维能力”的工具。通过 OAM 中应用的可交付对象 - Application Configuration,我们可以轻松的掌握我们的应用到底有那些 Kubernetes 工作负载组成,这些工作负载都使用了哪些运维特性,这些内容都会以 Kubernetes API 对象的形式展示,查看起来和查看 Deployment 与 Service 资源一样方便。

      Application Configuration

      关注点分离

      在实践中,如果基础架构和应用是由不同团队维护的,由于各个团队的关注点不同、对 Kubernetes 了解的程度不同、使用习惯不同,很容易产生混乱。实际上,对于业务研发人员和运维人员而言,他们并不想配置这些如此底层的资源信息,而希望有更高维度的抽象。这就要求一个真正面向最终用户侧的应用定义,一个能够为业务研发和应用运维人员提供各自所需的应用定义原语。

      通过组件(Component)和运维特征(Trait)将业务研发人员与运维人员关注的不同特征进行分离,再将不同的运维特征(Trait)与业务组件(Component)进行绑定,最终再由OAM 可交付物 – Application Configuration 将所有组件组装为一个统一的应用。研发与运维对资源的控制进行细粒度的划分,可以有效的解决实际情况中存在的类似”我比你更懂 Kubernetes,要听我的“的现象,避免了研发与运维之间的甩锅与扯皮的情况。

      面向最终用户的应用管理平台

      这部分白皮书中并未详细提及,但这也是我们现阶段的主要工作和努力方向,经过不到一年的时间,OAM 的概念、思想已经基本成熟,而基于 OAM 的实现也已经出现 - Crossplane 项目,该项目目前为 CNCF 的 Sandbox 项目。

      Crossplane 的出现解决了平台维护者,也就是负责维护 Kubernetes 的基础设施工程师的难题。但是对于应用研发和运维人员,也就是 OAM 的最终用户,操作起来并不是十分的友好。基础设施工程师为他们提供了一堆 CRD,他们必须逐个去挑选、测试和甄别,尤其是一些运维特征(Trait)可能存在功能冲突,不能同时与一个业务组件(Component)绑定,这都都要应用研发和运维人员自己去学习和测试,虽然可以通过文档来规范,但显然这样做并不优雅,这时 OAM App Engine(暂定名 RdurX)就出现了。

      OAM App Engine 所在位置

      OAM App Engine 的目标用户群体是应用开发者,是希望终端开发者用户可以感受到 OAM 提倡的各类应用管理理念带来的价值。相比于其他基于 K8s 的应用管理平台(如 rio ),OAM App Engine 将至少具备如下三大核心价值。

      1. 插件系统:App Engine 可以通过 OAM 具备快速纳管 operator 的能力,轻松扩展各种能力。
      2. 用户体验:贴近开发者,一切设计以最终开发者使用体验至上,复杂的概念做抽象,用户熟悉的概念不隐藏。
      3. 最佳实践:App Engine 将成为 OAM 实现的最佳实践。

      OAM 架构

      OAM App Engine 由 CLI 命令行工具、 Dashboard UI 管理页面和一系列编排文件/DSL 组成,目前还处于功能设计与开发当中,预计在8月底会和用户见面。OAM App Engine 的开发者均来自 OAM 中国社区,来自不同的公司和组织,是真正的从社区中来,服务社区用户。

      欢迎对 OAM 有兴趣的朋友加入,社区每双周都会进行视频例会,欢迎大家发表自己的见解或提出相关疑问。

      OAM 中国社区

      结语

      《云原生架构白皮书》的编写集合了阿里云众多技术专家,基于这些年阿里云海量的技术实践,对云原生这一当下十分火爆概念进行了十分深入的剖析,在分享知识和实践经验的同时还对云原生相关技术、架构设计和发展趋势等内容进行分析和描述,为那些对于云原生这一概念还十分陌生和迷茫的开发者/管理者提供了一份干货满满的参考资料。这里借用白皮书序言里的一句话:

      云计算的下一站,就是云原生;

      IT 架构的下一站,就是云原生架构 ;

      希望所有的开发者、架构师和技术决策者们,共同定义、共同迎接云原生时代。

      《云原生架构白皮书》下载链接 : https://developer.aliyun.com/topic/download?id=721

      <img src="/media/20200727145559VQF.gif" style="width: 150px;">

      ]]>
      用 Arthas “庖丁解牛” Fri, 20 Jun 2025 02:20:33 +0800 生产环境的 bug 开发环境无法复现怎么办?关键位置没有打印日志信息不足怎么办?莫慌,骚年。让强大的 Arthas法师来 carry,带你去生产环境"遨游"闯关。

      刚接触 Arthas,就被它能够 watch 方法的输入参数和返回值的功能震惊到了。这简直太酷炫了,让你可以像本地单步调试一样,跟踪到每一步的执行结果和获取当前的变量数值。以前要定位线上问题,信息不足就需要加日志打印,定位问题,可能需要反复重启应用。用了 Arthas,根本不需要加日志打印,重启应用这些操作。花了大概一个周末下午,在本地跑了下官方 demo,熟悉了下常用操作。脑子里对 Arthas 能够做什么,能解决什么,怎么解决,已经有了大概的了解。后面需要用的时候,就能派上大用场了(学了就一定有 bug 会找上门的=.=)。

      下面介绍一个特意找上门的 bug。

      背景:同一个聊天交友类产品,对外以一个主品牌以及多个新品牌进行发布。服务端是共用一套数据的,但是所有对外展示的信息,涉及到品牌相关的,需要进行文案替换。在同一个群组里,主品牌和新品牌的用户可以互相聊天。

      问题现象:

      • 线上某个群组里面,同一条聊天消息涉及到需要替换文案的内容时,主品牌侧的用户有的显示正常,有的显示为新品牌文案;
      • 不同的群聊天消息,同一个用户有的展示正常,有的异常;
      • 新品牌侧的用户看到的群聊天消息文案替换正常。

      先贴下相关的代码(用 Arthas 的 jad 直接反编译的源码):

      群聊消息下发方法:

      image.png

      write 方法文案替换逻辑代码:

      image.png

      PublishMessage 是下行消息类,replaceMsgMap 是提前生成好的各个新品牌对应文案,主品牌使用原始消息文案。

      乍一看,文案替换逻辑没啥毛病(但问题就在这,大家可以先思考下),感觉自己又要去面对一个扑朔迷离的玄学 bug 了(永远不要把程序 bug 归结为灵异事件)。代码看不出问题,本地单步调试鼓捣了一早上,也没复现出来,看来只能在生产环境定位了,Arthas 要登场了。

      由于生产环境的消息转发量很大,直接 attach 进程风险太高,且不利于单条消息观察定位。所以选择预发布环境进行 attach,请求量可控,数据和线上一致,也只有读操作,不会影响到生产环境。

      用 Arthas 的 watch 命令,观察 write 方法的输入参数。

      image.png

      • -x 表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是 1
      • -b 表示观察方法调用前

      可以看到,publishMessage、userSession 参数的值都显示出来了。接着就可以在预发布触发消息下行进行数据观察了。

      建了个测试群,除了自己一个主品牌的测试用户还有另外一个新品牌用户。最初开始发送了几条群聊消息都正常,后面又拉了一个新品牌用户以及主品牌测试用户,复现的概率就高了许多。观察了下同一条群聊消息发给每个群成员的 publishMessage 值,发现如果先遍历到了新品牌用户,再遍历到主品牌用户时,publishMessage 的文案居然是新品牌文案!!!心里猛的一惊,是了,就是这个低级错误造成的 bug,大家应该也猜到原因了。

      下面揭晓下这个问题产生的原因:

      • 遍历群成员传递的 publishMessage 形参,每次改变 payload 都会影响到被传递进去的 publishMessage 实参
      • replaceMsgMap 里只存储了新品牌文案
      • 主品牌根据 appName 获取对应文案时为空,则不设置 payload,使用最传递进来的 publishMessage 的 payload
      • 遍历群组成员时,顺序是随机性的

      如果某个主品牌用户在新品牌用户之后被遍历到,那么 publishMessage 的 payload 字段就会被设置为新品牌文案。而主品牌在 replaceMsgMap 里找不到对应文案,就不更新 payload了,复用了上一次被遍历用户的 payload,就会出现文案显示异常。

      知道原因后就好处理了,replaceMsgMap 里把主品牌的文案也加进去,每次遍历到主品牌,也更新 payload 字段,保证文案正常显示。

      整个定位过程,无需增加 log 日志,线上应用也无需重启,便能获取足够的信息进行问题排查定位,是不是贼好用了。花个半天时间,摸索鼓捣下,要用时就能省下不少工夫。详细使用文档见->官方文档

      一键安装并启动 Arthas

      • 方式一:通过 Cloud Toolkit 实现 Arthas 一键远程诊断

      Cloud Toolkit 是阿里云发布的免费本地 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。通过插件,可以将本地应用一键部署到任意服务器,甚至云端(ECS、EDAS、ACK、ACR 和 小程序云等);并且还内置了 Arthas 诊断、Dubbo工具、Terminal 终端、文件上传、函数计算 和 MySQL 执行器等工具。不仅仅有 IntelliJ IDEA 主流版本,还有 Eclipse、Pycharm、Maven 等其他版本。

      推荐使用 IDEA 插件下载 Cloud Toolkit 来使用 Arthas:http://t.tb.cn/2A5CbHWveOXzI7sFakaCw8

      • 方式二:直接下载

      地址:https://github.com/alibaba/arthas

      本文转自<阿里巴巴云原生技术圈>——阿里巴巴云原生小助手

      ]]>
      IoT 设备发送 MQTT 请求上云的曲折经历 Fri, 20 Jun 2025 02:20:33 +0800 IoT 设备发送 MQTT 请求上云的曲折经历

      为了搞清楚 IoT 设备从传感器获取到数据,通过网络发送到云端的整个网络过程,我们先来看一下网络分层模型:

      上图中例举了网络分层中最常见的协议:

      • 应用层:应用程序负责将数据以相应规则(协议)进行包装,发给传输层

        • MQTT:消息队列遥测传输
        • CoAP:受限应用协议
        • HTTP:超文本传输协议
        • FTP:文件传输协议
        • SMTP:简单邮件传送协议
      • 传输层:负责将应用层传过来的数据进行分组,为确保终端接收数据的顺序和完整性,会对每个分组进行标记,交给网络层

        • TCP:传输控制协议
        • UDP:用户数据协议
      • 网络层:负责将传输层发来的数据分组发送到目标终端

        • IP:网际协议
        • ICMP:互联网控制报文协议
        • IGMP:互联网组管理协议
      • 链路层:为网络层发送和接收数据单元

        • ARP:地址解析协议
        • RARP:逆地址解析协议


        封装 和 分用  


      数据在经过每一层的时候都要被对应的协议包装,即封装 (Encapsulation),到达终端的时候,要一层一层的解包,即分用(Demultiplexing)。


      发送时,设备采集的业务数据被应用程序封装为 MQTT 报文,每一层会将上层传过来的报文作为本层的数据块,并添加自己的首部,其中包含了协议标识,这一整体作为本层报文向下传递。


      接收时,数据自下而上流动,经过每一层时被去掉报文首部,根据报文标识确定正确的上层协议,最终到应用层被应用程序处理。





      IoT设备采集的业务数据被设备端上的应用程序封装为 MQTT 报文,MQTT 报文会以数据流的形式通过一条已经建立的TCP长连接按序传输,TCP收到数据流后会将其分割成小的数据块,每个小块被添加的TCP首部与数据块共同组成了TCP分组,分组经由网络层发送,网络层遵循IP协议,当收到分组发送请求后,会将分组其放入IP数据报,填充报头,将数据报发经由链路层发送出去。


      云端系统从链路层接收到数据请求后,进入网络层对数据进行解析,交给给传输层,校验分组顺序和完整性,从数据块中取出数据,得到MQTT报文,交给应用层进行处理。这个过程会逐层剥离报头还原出IoT设备采集的业务数据。


        应用层 -MQTT协议  


      MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。


      MQTT 协议的数据包格式非常简单,由固定报头(Fixed header)、可变报头(Variable header)、有效载荷(Payload)三个部分组成。



      固定报头:包含控制报文类型,标志位和剩余长度。

      MQTT 报文的首字节高4位(bit7~bit4)表示控制报文类型,总共可以表示16种协议类型:



      MQTT 报文的首字节低4位(bit4~bit0)用于指定数据包的标志位,仅PUBLISH控制报文有使用。

      剩余长度: MQTT 报文的第2 字节开始是用于标识 MQTT 数据包长度的字段,最少一个字节,最大四个字节,每一个字节的低 7 位用于标识值,范围为 0~127。



      可变报头:存在于部分类型的 MQTT 数据包中,具体内容由相应类型的数据包决定。


      有效载荷:存在于部分 MQTT 数据包中,存储消息的具体业务数据。


        传输层-TCP 协议  **


      MQTT 连接是建立在 TCP 连接的基础之上的,TCP 提供可靠的数据连接。当要传输一个 MQTT 报文时,报文数据会以流的形式通过一条已经打开的TCP连接按顺序传输,TCP会将收到的数据分成小块,每块是一个TCP分组。


      由于数据是分成小块发送的,所以完整可靠的数据传输主要体现在:分组是否完整、分组顺序是否正常、分组是否损坏、分组数据是否重复。这些可以通过TCP的检验和、序列号、确认应答、重发控制、连接管理和窗口机制来控制。


      TCP是传输控制协议,传输控制主要依赖首部包含的6个标志,它们控制报文的传输状态,以及发送端和接收端应对数据采取的动作。当它们的值为1时,标志对应的各自功能才允许被执行,比如当URG为1时,报文首部的紧急指针部分才有效。

      • URG 紧急指针
      • ACK 确认序号有效
      • PSH 接收方应该尽快将这个报文段交给应用层。
      • RST 重建连接
      • SYN 同步序号用来发起一个连接
      • FIN 发端完成发送任务

      image.png
      源端口和目的端口:标识发送方和接收方的端口号,一个TCP连接通过4个值确认:源IP、源端口、目的IP、目的端口,其中源IP和目的IP包含在IP分组内。


      首部长度:表示TCP首部的字节长度,也能标记出从多少个字节开始,才是需要传输的数据。


      TCP段序号:本段报文发送的数据第一个字节的序号,每段报文中的数据的每个字节都有序号,第一个字节的序号从0开始,依次加1,加到2的32次方减1后再次从0开始。


      TCP段确认序号 :当首部标志ACK为1时,确认序号有效。TCP段被接收端接收后,会回送给发送端一个确认号,为上次接受的最后一个字节序号加1。
      检验和:由发送端计算,接收端验证,如果接收方检测到检验和不正确,表明该TCP段可能有损坏,会被丢弃,同时接收端向回送一个重复的确认号(与最近的一次正确的报文传输的确认号重复),表明接收到的TCP段是错误的,并告知自己希望收到的序号。这时发送端需要立即重传出错的TCP段。


      紧急指针:当首部标志URG为1时,紧急指针有效,表示发送端向接收端要发送紧急数据。紧急指针是一个正偏移量,它和TCP段序号相加,计算出紧急数据的最后一个字节的序号。比如接收方接收到数据,从序号为1000的字节开始读取,紧急指针为1000,那么紧急数据就是序号从1000到2000之间的字节。这些数据由接收方决定如何处理。


      窗口尺寸:决定了TCP一次成块数据流的吞吐量。需要注意的是,它表示的是发送一方的允许对方发送的数据量,比如发送方首部中的窗口大小为1000,就表示发送方最多可以接受对方发来的1000个字节的数据量。这与发送方的数据缓存空间有关,会影响TCP的性能。


      首部标志PSH:如果需要告诉接收方将数据立即全部提交给接收进程,发送方需要将PSH置为1,这里的数据是和PSH一起传送的数据以及之前接收到的全部数据。如果接收方收到了PSH为1的标志,需要立即将数据提交给接收进程,不用再等待有没有其他数据进来。


      复位标志RST:当RST为1时,表示连接出现了异常情况,接收方将终止连接,通知应用层重新建立连接。


      同步序号SYN:用来建立连接,涉及到TCP的三次握手。

      1. 开始建立连接时,客户端向服务器发送一个TCP分组,分组首部的SYN为1,并携带一个初始序号,表明这是一个连接请求。
      2. 如果服务器接受了连接,会向客户端发送一个TCP分组,分组中会包含SYN和ACK,都为1,同时包含一个确认序号,值为来自客户端的初始序号 + 1,表示连接已经被接受。
      3. 客户端收到上一步发来的分组后,会再向服务器发送一段确认报文分组,ACK为1,会再次携带确认序号,值是第二步来自客户端的确认序号 + 1。服务端收到确认信息后,进入已经连接的状态。


      在第三步的确认分组中,是可以携带要发送的数据的。


      连接终止标志FIN:用来关闭连接,当一端完成数据发送任务后会发送一个FIN标志来终止连接,但因为TCP在两个方向(C-S,S-C)上会有数据传递,每个方向有各自的发送FIN & 确认关闭流程,所以会有四次交互,也称为四次挥手。

      1. 如果客户端应用层的数据发送完毕,会导致客户端的TCP报文发送一个FIN,告知服务器准备关闭数据传送。
      2. 服务器接收到这个标志后,它发回一个ACK,确认序号为收到的序号加1,同时TCP还要向应用程序发一个文件结束符。
      3. 此时服务器关闭这个方向的连接,导致它的TCP也会发送一个FIN。
      4. 客户端接收到之后发回一个确认ACK,序号为收到的序号 + 1,连接完全关闭。


      TCP段序号与确认序号保证了数据的顺序,检验和确保数据的完整性,紧急指针保证紧急数据可被及时处理。另外,TCP还有一些超时重传、 拥塞避免、慢启动的机制,都可以保证分组数据按照顺序完整的传到目标端。


        网络层- IP 协议  


      如果说TCP分组是包装货物的集装箱,那么IP就是运送集装箱的卡车。IP协议提供了两个节点之间的连接,保证将TCP数据尽可能快地从源端送到终端,但却不能保证传输的可靠性。


      IP层会将上层传过来的TCP分组封装,带上自己的首部,再进行选路、是否分片以及重组的工作,最终到达目的地,这个过程中,IP首部起了重要的作用,下面让我们看一下首部的结构。

      版本:表示当前IP协议的版本,目前版本号是4,还有一种是6,也就是IPV4和IPV6,如果发送和接收这两端的版本不一致,那么当前IP数据报会被丢弃。


      首部长度:整个首部的长度,最长为60字节。


      服务类型(TOS):用来区分服务的类型,但其实IP层在工作的时候一直没有实际使用过,现有的TOS只有4bit的子字段,和1bit的未用位。未用位必须置为0。TOS的4个bit中只能将一个置成1,用来表示当前服务类型。4bit对应的4个服务类型分别为:最小时延、最大吞吐量、最高可靠性和最小费用。


      总长度:表示当前的数据报报文的总长度,单位为字节,可以结合首部长度计算出报文内数据的大小以及起始位置。


      下面这三个首部字段涉及到IP数据报的分片与重组过程,由于网络层一般会限制每个数据帧的最大长度,IP层发送数据报会在选路的同时查询当前设备网络层的每个数据帧的最大传输长度,一旦超出,数据报就会被进行分片,到达目的地之后再进行重组,此时就会用以下三个字段作为重组依据。需要注意的是:因为存在选路的过程,数据报经过的每层路由设备对于数据帧的最大传输长度都不同,所以分片可能发生在任意一次选路的过程中。


      分组标识:这个标识相当于ID,每成功发送一个分片,IP层就会把这个分组ID加1。


      标志:共占用三位,分别是R、D、M,R目前还没有被使用,有用的是D、和M。这个字段表示了数据报的分片行为。D如果为1的话,表示数据无需分片,一次传输完;M如果为1,表示数据是分片的,后边还有数据,当它为0时,就表示当前数据报是最后一个分片,或者只有这一个分片。


      片偏移:标识了当前分片距离原始数据报开始处的位置,分片之后,每一片的总长度会改成这一片的长度值,而不是整个数据报的长度。


      生存时间:(TTL) 可以决定数据报是否被丢弃。因为IP发送数据是逐跳的,数据有可能在被设置了路由功能的不同的IP层之间转发,所以生存时间表示了数据报最多个可以经过多少个处理过它的路由,每经过一层路由,值减去1,当值为0时数据报就被丢弃,并且发送一个带有错误消息的报文(ICMP,IP层的组成部分,被用来传递一些错误信息)给源端。生存时间可以有效解决数据报在一个路由环路中一直转发的问题。


      首部检验和:校验数据报的完整性,发送端对首部进行求和,将结果存在检验和中,接收端再计算一遍,如果计算结果与存在检验和中的结果一致,则说明传输过程是OK的,否则这个数据报就会被丢弃。


      上层协议:决定了接收端在分用的时候将数据交给哪个上层协议去处理,例如TCP或者UDP。


      源IP:记录了发送端的IP,在回送错误消息时用到。


      目的IP:表示目的IP,每一次选路都要以它来做决策。

      路由选择


      因为IP首部只包含了目的IP地址,并不体现完整的路径,当向外发送数据时,IP层会根据目的IP在本机路由表中的查询结果来做出选路决策,数据报会逐跳地被运送到目的地,这里的每一跳,就是一次路由选择。


      IP层既可配置成路由器,也可以配置成主机。当配置成路由功能时,可以对数据报进行转发,配置成主机时,如果目的IP不是本机IP,数据报会被丢弃。


      具有路由功能的IP层在当目标IP不是本机地址的时候是根据什么判断转发到哪一站呢?要理解这个问题,需要先明白路由表的结构,以下是IP层维护的路由表:

      • Destination(目的IP):表示IP数据报最终要到达或者经过的网络地址或者主机地址。
      • Gateway(下一跳地址):当前维护路由表设备的相邻路由器的地址
      • Flags(标志):表示当前这一条路由记录的属性,具体用五个不同的标志来表示:

        • U:该路由可以使用
        • G:如果有这个标志,表示是下一跳是一个网关,如果没有,表示下一跳是和当前设备在一个网段,也就是可以直接把数据报发过去
        • H: 下一跳是一个主机还是一个网络,有这个标志,表示主机,没有,则表示下一跳的路由是一个网络
        • D:该路由是由重定向报文创建的
        • M:该路由已被重定向报文修改
      • Interface:当前路由项的物理端口


      每收到一个数据报时候,IP层就会根据目的IP在路由表里查询,根据查询状态会导向三种结果:

      • 找到了与目的IP完全匹配的路由项,将报文发给该路由项的下一站路由(Gateway)或者网络接口(Interface)
      • 找到了与目的IP的网络号匹配的路由项,将报文发给该路由项的下一站路由(Gateway)或者网络接口(Interface)
      • 前两者都没有找到,就看路由表里有没有默认路由项(default),有的话发给它指定的下一站路由(Gateway)


      要是上边三个都没有结果,那么数据报就不能被发送。IP数据报就是这样一跳一跳地被送往目的主机的,但数据报有固有的长度,一旦超出了目的主机的MTU,就会被分片。

      数据报分片的概念


      TCP在进行握手的时候,会根据目的端IP层的最大传输单元(MTU)来决定TCP数据每次能传输的最大数据量(MSS),之后TCP会对数据依照MSS来进行分组,每个分组会被包装进一个IP数据报内。当IP数据报经过选路过程中的任意一层路由时,有可能被MTU限制住从而被分片,这时IP首部的3bit标志中的M标志被置为1,表示需要分片。每个分片的首部基本一样,只是片偏移有所不同。依据片偏移,这些分片在目的端被重组成一个完整的IP数据报(一个TCP分组)。IP传输是无序的,所以得到的数据报也是无序的,但如果数据完整,TCP会根据首部中的字段对其进行排序。一旦IP分片丢失,IP层无法组成完整的数据报,就会告诉TCP层,TCP进行重传。


        链路层-ARP 协议  **


      当IP层将数据封装好之后,只有目标主机的IP地址。光有IP地址并不能直接把数据报发送过去,因为每一台硬件设备都有自己的MAC地址,是一个48bit的值。现在知道目标IP的地址,需要找到这个IP对应的MAC地址。这个过程要通过查询路由表,再结合链路层的ARP协议,最终获得目标IP对应的MAC地址。
      ARP协议实现了从IP地址到MAC地址的映射。一开始,起点并不知道目标的MAC地址,只有目标IP,要获取这个地址就涉及到了ARP的请求和应答。同样,ARP也有自己的分组,先看一下分组格式。

      以太网目的地址: 目的端的MAC地址,当ARP缓存表中没有的时候,这里为广播地址。


      以太网源地址: 发送端的MAC地址。


      帧类型: 不同的帧类型有不同的格式和MTU值,不同的类型有不同的编号,这里ARP对应的编号是0x0806。


      硬件类型: 指链路层网络类型,1为以太网。


      协议类型: 指的是要转换的地址类型,0x0800为IP地址。比如将以太网地址转换为IP地址。


      操作类型: 有四种,分别是ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)。


      源MAC地址: 表示发送端MAC地址。


      源IP地址: 表示发送端IP地址。


      目的以太网地址: 表示目标设备的MAC物理地址。


      目的IP地址: 表示目标设备的IP地址。


      当两台设备发送报文之前,源端的链路层会用ARP协议去询问目的端的MAC地址,ARP会将一个请求广播出去,以太网上的每一个主机都会收到这份广播,广播的目的是询问目标IP的MAC地址,内容主要是先介绍自己的IP和MAC地址,再询问如果你有目标IP,请回复你的硬件地址。如果一个主机收到广播后看到自己有这个IP,并且请求内有源IP和MAC地址,那么就会向源主机回应一个ARP应答。如果没有目标IP,就会丢弃这个请求。可以看出请求是向外广播的,而应答是单独回应的。


      但不能每次通信之前都去经历一次请求-应答过程,在成功地接收到应答之后,IP和MAC地址的映射关系就会缓存在ARP缓存表中,有效期一般为20分钟,便于网络层下次直接进行封装,所以,完整的过程应该是:


      IP层接收到TCP分组后,发送或者封装之前,通过查询路由表:

      • 当目标IP和自己在同一个网段时,先去ARP缓存表里找有没有目标IP对应的MAC地址,有的话交给链路层进行封装发送出去。如果缓存表内没有,进行广播,获得MAC地址后缓存起来,IP层再对TCP进行封装,然后交给链路层再封装发送出去。
      • 当目标IP和自己不在同一个网段,需要将报文发给默认的网关。如果ARP缓存表中有网关IP对应的MAC地址,那么交给链路层进行封装发送出去。如果没有,进行广播,获得地址后缓存起来,IP层再对TCP进行封装,然后交给链路层再封装发送出去。

      以太网数据帧


      上面所有东西都准备好了,封装发送的其实是以太网数据帧。以太网目的地址、以太网源地址、帧类型这三者组成了帧首部。在首部之前还会插入前同步码和帧开始定界符,告知接收端做一些准备工作。帧检验序列 FCS被添加进尾部,用来检测帧是否出错。

      前同步码: 协调终端接收适配器的时钟频率,让它与发送端频率相同。


      帧开始定界符: 帧开始的标志,表示帧信息要来了,准备接收。


      目的地址: 接收帧的网络适配器的MAC地址,接收端收到帧时,会首先检查目的地址与本机地址是否相符,不是的话就会丢弃。


      源地址: 发送端设备的MAC地址。


      类型: 决定接收到帧之后将数据交由那种协议处理。


      数据: 交给上层的数据。在本文的场景中指IP数据报。


      帧检验序列: 检测这一帧是否出错,发送方计算帧的循环冗余码校验(CRC)值,把这个值写到帧里。接收方计算机重新计算 CRC,与 FCS 字段的值进行比较。如果两个值不相同,则表示传输过程中发生了数据丢失或改变。这时,就需要重新传输这一帧。

      传输和接收

      **

      • 接收到上层传过来的数据报之后,根据MTU以及数据报大小来决定是否分割成小块,也就是IP数据报被分片的过程。
      • 把数据报(块)封装成一帧,传给底层组件,底层组件将帧转换为比特流,并发送出去。
      • 以太网上的设备接收到帧,检查帧里边的目标地址,如果与本机地址匹配,帧就会被处理,一层一层向上传递(分用过程)。


      结语


      以上,我们梳理了 IoT 设备将传感器采集数据,被端上应用程序封装成 MQTT 报文,通过网络协议一层层封装,再到云端接收系统一层层拆分的完整网络过程,希望对大家认识物联网相关 MQTT,TCP,IP,ARP 网络协议有所帮助。


      【往期回顾】
      1、39张IoT传感器工作原理GIF图汇总
      2、自建 MQTT 集群 迁移 阿里云IoT实践
      3、智能手持测温枪开发实践
      4、JMeter 压测 MQTT 服务性能实战
      5、IoT物联网平台日志服务详解
      6、工业 Modbus,电力104规约, 车联网JT808
      7、网关与子设备上云开发实战
      8、IoT中实现 M2M 设备之间联动

      ]]>
      掌门教育微服务体系 Solar | 阿里巴巴 Nacos 企业级落地上篇 Fri, 20 Jun 2025 02:20:33 +0800 头图.png

      联席作者:吴毅挺 任浩军 张彬彬 廖梦鸽 张金星 胡振建
      郑重鸣谢:Nacos - 彦林,Spring Cloud Alibab - 小马哥、落夜,Nacos 社区 - 张龙(pader)、春少(chuntaojun)

      前言

      在高速发展的时候,公司规模越来越大,老师人数越来越多,这时候公司不能铺太多人去做运营与服务,必须提高每个人效,这就需要技术驱动。因此掌门教育转变成一家技术驱动型的公司,如果被迫成为一家靠资金驱动的公司就活不下去了。
      -- 张翼(掌门教育创始人兼 CEO)

      掌门教育自 2014 年正式转型在线教育以来,秉承“让教育共享智能,让学习高效快乐”的宗旨和愿景,经历云计算、大数据、人工智能、 AR / VR / MR 以及现今最火的 5G ,一直坚持用科技赋能教育。掌门教育的业务近几年得到了快速发展,特别是今年的疫情,使在线教育成为了新的风口,也给掌门教育新的机遇。

      随着业务规模进一步扩大,流量进一步暴增,微服务数目进一步增长,使老的微服务体系所采用的注册中心 Eureka 不堪重负,同时 Spring Cloud 体系已经演进到第二代,第一代的 Eureka 注册中心已经不大适合现在的业务逻辑和规模,同时它目前被 Spring Cloud 官方置于维护模式,将不再向前发展。如何选择一个更为优秀和适用的注册中心,这个课题就摆在了掌门人的面前。经过对 Alibaba Nacos 、HashiCorp Consul 等开源注册中心做了深入的调研和比较,最终选定 Alibaba Nacos 做微服务体系 Solar 中的新注册中心。

      背景故事

      1. 掌门教育微服务面临的挑战

      1)第一次生产事故

      2020 年疫情爆发后的几个月后,掌门教育的微服务实例数比去年猛增 40% ,基础架构部乐观的认为注册中心 Eureka 服务器可以抗住该数量级的实例数规模, Eureka 服务器在阿里云 PROD 环境上执行三台 8C16G 普通型机器三角结构型对等部署,运行了好几年都一直很稳定,但灾难还是在2020年3月某天晚上降临,当天晚上大概 9 点 30 分左右,其中两台 Eureka 服务器无征兆的 CPU 占用迅速上升到100%,同时大量业务服务掉线,告警系统被触发,钉钉机器人告警和邮件告警铺天盖地而来。基础架构部和运维部紧急重启 Eureka 服务器,但没多久,CPU 依旧没抗住,而且更加来势凶猛,打开的文件描述符数瞬间达到 8000+ ,TCP 连接达到 1 万+ ,业务服务和 Eureka 服务器的通信产生大面积的 TCP CLOSE_WAIT 事件,且伴有大量 Broken pipe 异常。

      1.png

      2.png

      3.png

      org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe

      运维人员尝试把机器升级成增强型 8C16G ,折腾一番后,于 23:00 左右恢复正常。

      4.png

      5.png

      2)第二次生产事故

      微服务实例数依旧在增长, Eureka 服务器平稳运行了大概半个月后,灾难又一次降临,CPU 再次飙升到100%,过程就不表述了。处理方式,把机器升级成增强型 16C32G,并把 Eureka 服务器的版本升级到 Spring Cloud Hoxton 版,并优化了它的一些配置参数,尔后事件再也没出现。

      2. 掌门教育新微服务演进思考

      虽然 Eureka 服务器目前运行平稳,但我们依旧担心此类事故在未来会再次发生,于是痛定思痛,经过深入的调研和比较一段时间后,通过由基础架构部牵头,各大业务线负责人和架构师参与的专项注册中心架构评审会上,CTO 拍板,做出决议:选择落地 Alibaba Nacos 作为掌门教育的新注册中心。

      Talk is cheap,show me the solution。基础架构部说干就干,Nacos 部署到 FAT 环境后,打头阵的是测试组的同学,对 Nacos 做全方位的功能和性能测试,毕竟 Nacos 是阿里巴巴拳头开源产品,迭代了2年多,在不少互联网型和传统型公司都已经落地,我们选择了稳定的 1.2.1 版本,得出结论是功能稳定,性能上佳,关于功能和性能方面的相关数据,具体参考:《掌门1对1微服务体系 Solar | 阿里巴巴 Nacos 企业级落地下篇》。

      6.png

      7.png

      但是,如何迁移 Eureka 上的业务服务到 Nacos 上?业务服务实例数目众多,迁移工作量巨大,需要全公司业务部门配合,同时 Eureka 对注册的业务服务名大小写不敏感,而 Nacos 对注册的业务服务名大小写敏感,那么对于业务服务名不规范的业务部门需要改造。而对于基础架构部来说, Nacos Eureka Sync 方案如同一座大山横亘在我们面前,是首先需要迈过去的坎,纵观整个过程,该方案选型还是折腾了一番,具体参考:《掌门1对1微服务体系 Solar | 阿里巴巴 Nacos 企业级落地中篇》。

      阿里巴巴 Nacos 企业级落地的优化代码,在不久的将来会通过开源的方式回馈给业界。

      官方介绍

      1. Nacos 简介

      阿里巴巴中间件部门开发的新一代集服务注册发现中心和配置中心为一体的中间件。它是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施,支持几乎所有主流类型的“服务”的发现、配置和管理,更敏捷和容易地构建、交付和管理微服务平台。

      • Nacos Landscape

      8.png

      • Nacos Map

      9.jpg

      摘自官网 What is Nacos:https://nacos.io/en-us/docs/what-is-nacos.html

      2. Spring Cloud Alibaba 简介

      阿里巴巴中间件部门开发的 Spring Cloud 增强套件,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba ,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

      10.png

      摘自官网 Spring Cloud Alibaba Introduction:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/introduction.adoc

      关于 Nacos 和 Spring Cloud Alibaba 如何使用,它的技术实现原理怎样等,官方文档或者民间博客、公众号文章等可以提供非常详尽且有价值的材料,这些不在本文的讨论范围内,就不一一赘述。笔者尝试结合掌门教育现有的技术栈以及中间件一体化的战略,并着眼于强大的 Nacos 和 Spring Cloud Alibaba 技术生态圈展开阐释。

      Nacos 开发篇

      1. Nacos Server 落地

      1)Nacos Server

      • Nacos Server 环境和域名

      掌门的应用环境分为 4 套,DEV | FAT | UAT | PROD 分别对应开发、测试、准生产环境、生产环境,因此 Nacos Server 也分为 4 套独立环境。除了 DEV 环境是单机部署外,其他是集群方式部署。对外均以域名方式访问,包括 SDK 方式连接 Nacos Server 和访问 Nacos Server Dashboard 控制台页面。

      • Nacos Server 环境隔离和调用隔离

      Nacos Server 可以创建不同的命名空间,做到同一个应用环境的基础上更细粒度的划分,隔离服务注册和发现。在某些场景下,开发本地有需要连接测试环境的 Nacos Server ,但其他测试服务不能调用到开发本地,这时候可以将 NacosDiscoveryProperties 的 enabled 属性设置为 false 。

      • Nacos Server 集成 Ldap

      Nacos Server Dashboard 集成公司的 Ldap 服务,并在用户首次登录时记录用户信息。

      2)Nacos Server 界面

      • Nacos 界面权限

      Nacos Server Dashboard 用户首次登陆时,默认分配普通用户(即非 ROLE_ADMIN )角色,对查询以外的按钮均无操作权限,以免出现误操作导致服务非正常上下线。

      • Nacos 界面显示服务概览

      Nacos Server Dashboard 页面增加服务总数及实例总数的统计,该信息每 5 秒刷新一次。

      11.png

      3)Nacos 监控

      【Nacos Server 监控】

      • 标准监控

      基于公司现有的 Prometheus 、 Grafana 、 AlertManager 从系统层监控 Nacos。

      12.png

      • 高级监控

      根据 Nacos 监控手册,结合 Prometheus 和 Grafana 监控 Nacos 指标。

      13.png

      【Nacos Eureka Sync Etcd 监控】

      从如下界面可以监控到,业务服务列表是否在同步服务的集群上呈现一致性 Hash 均衡分布。

      14.png

      4)Nacos 日志

      • 日志合并及 JSON 格式化

      将 Nacos 多模块的日志统一按 info 、 warn、error 级别合并,定义 schema 字段标记不同模块,按 JSON 格式滚动输出到文件,供 ELK 采集展示。

      15.png

      5)Nacos 告警

      【Nacos Server 告警】

      • 业务服务上下线的告警

      16、17.jpg

      • Nacos Eureka Sync 告警

      18、19.jpg

      • 服务名大写告警

      20、21.jpg

      • 业务服务同步完毕告警

      22(1).jpg

      2. Nacos Client 落地

      1)Solar Nacos SDK 环境初始化

      应用接入 Solar Nacos SDK 在启动时需要初始化完成 Nacos Server 的连接配置,即 spring.cloud.nacos.discovery.server-addr 参数的赋值。不同环境下连接的 Nacos Server ,因此需要读取机器所在的 env 环境参数,来选择相对应的 Nacos Server 地址。

      初始化逻辑代码如下:

      public class NacosClientConfigApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
          private static final Logger logger = LoggerFactory.getLogger(NacosClientConfigApplicationContextInitializer.class);
          @Override
          public void initialize(ConfigurableApplicationContext applicationContext) {
              try {
                  Properties props = new Properties();
                  String path = isOSWindows() ? CommonConstant.SERVER_PROPERTIES_WINDOWS : CommonConstant.SERVER_PROPERTIES_LINUX;
                  File file = new File(path);
                  if (file.exists() && file.canRead()) {
                      FileInputStream fis = new FileInputStream(file);
                      if (fis != null) {
                          try {
                              props.load(new InputStreamReader(fis, Charset.defaultCharset()));
                          } finally {
                              fis.close();
                          }
                      }
                  }
                  String env = System.getProperty("env");
                  if (!isBlank(env)) {
                      env = env.trim().toLowerCase();
                  } else {
                      env = System.getenv("ENV");
                      if (!isBlank(env)) {
                          env = env.trim().toLowerCase();
                      } else {
                          env = props.getProperty("env");
                          if (!isBlank(env)) {
                              env = env.trim();
                          } else {
                              env = NacosEnv.DEV.getCode();
                          }
                      }
                  }
                  String serverAddr = NacosEnv.getValueByCode(env);
                  Map<String, Object> nacosClientPropertySource = new HashMap<>();
                  nacosClientPropertySource.put(CommonConstant.NACOS_DISCOVERY_SERVER_ADDR, serverAddr);
                  applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("solarNacosClientPropertySource", nacosClientPropertySource));
              } catch (Exception e) {
                  logger.error(e.getMessage());
              }
          }
          @Override
          public int getOrder() {
              return Ordered.LOWEST_PRECEDENCE;
          }
          private boolean isOSWindows() {
              String osName = System.getProperty("os.name");
              return !isBlank(osName) && osName.startsWith("Windows");
          }
          private boolean isBlank(String str) {
              return Strings.nullToEmpty(str).trim().isEmpty();
          }
      }

      2)Solar Nacos 蓝绿灰度发布和子环境隔离

      在 Nacos 和 Eureka 双注册中心过渡状态下, Solar SDK 支持跨注册中心调用的蓝绿灰度发布和子环境功能。下面的图片,只以 Eureka 为例:

      23.png

      我们只需要把 Eureka SDK 换到 Nacos SDK 即可,实现如下功能:

      • Solar 蓝绿灰度发布

        • 版本匹配灰度发布
        • 版本权重灰度发布
      • Solar 多区域路由

        • 区域匹配灰度路由
        • 区域权重灰度路由
      • Solar 子环境隔离

        • 环境隔离
        • 环境路由
      • Solar 版本号和区域值,子环境号策略

        • DEV 环境,Git 插件自动创建灰度版本号
        • DevOps 环境设置

      Solar 蓝绿灰度发布架构图:

      24.png

      Solar 基于版本维度的蓝绿灰度发布架构图:

      25.png

      Solar 子环境隔离架构图:

      26.png

      更多功能参考:

      掌门 1 对 1 微服务体系 Solar 第 1 弹:全链路灰度蓝绿发布智能化实践,掌门教育已经实现通过灰度蓝绿发布方式,实现对流量的精确制导和调拨。

      Nepxion Discovery 开源社区:https://github.com/Nepxion/Discovery

      3)Solar Nacos 集成 Sentinel

      27.jpg

      4)Solar Nacos 集成灰度蓝绿埋点到 Skywalking

      28.png

      29.png

      5)Solar Nacos 集成 Sentinel 埋点到 Skywalking

      • 微服务上的 Sentinel 埋点

      30.jpg

      31.jpg

      • 网关上的 Sentinel 埋点

      32.jpg

      33.jpg

      6)Solar Nacos 集成 DevOps 发布平台

      • 集成携程 VI Cornerstone 实现服务拉入拉出

      Solar Nacos SDK 的服务,在应用发布时需要做服务的拉入拉出,目的是为了发布时流量无损。掌门使用 VI Cornerstone 实现拉入拉出功能。具体实现是在初始化 NacosDiscoveryProperties 对象时设置 instance.enabled 属性值为 false,在服务完全初始化后,通过发布系统调用 Solar Nacos SDK 的 API 接口再修改为 true 来被外部发现并提供服务。

      public class NacosApplicationContextInitializer implements EnvironmentPostProcessor {
          @Override
          public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
              Boolean bootstrapEnabled = configurableEnvironment.getProperty("devops.enabled", Boolean.class, false);
              if (bootstrapEnabled) {
                  Properties properties = new Properties();
                  properties.put("spring.cloud.nacos.discovery.instanceEnabled", "false");
                  PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("devopsEnabledNacosDiscoveryProperties", properties);
                  MutablePropertySources mutablePropertySources = configurableEnvironment.getPropertySources();
                  mutablePropertySources.addFirst(propertiesPropertySource);
              }
          }
      }

      spring.factories 配置文件:

      org.springframework.boot.env.EnvironmentPostProcessor=
      com.ctrip.framework.cs.spring.NacosApplicationContextInitializer

      7)Solar Nacos SDK 接入

      【Solar 版本定义】

      • Solar 2.3.x & 1.3.x,基于 Nacos SDK
      • Solar 2.2.x & 1.2.x,基于 Eureka SDK

      【Solar 版本关系】

      • Solar 版本与 Spring Boot 技术栈的关系

      34.jpg

      • Solar 版本与注册中心的关系

      35.jpg

      【Solar SDK 接入】

      • 设置 Parent
      <parent>
          <groupId>com.zhangmen</groupId>
          <artifactId>solar-parent</artifactId>
          <version>${solar.version}</version>
      </parent>
      • 添加到 pom.xml

      只需引入一个 Jar 包,对接成本极低,只做基本组件封装,非常轻量级。

      微服务:

      <dependency>
          <groupId>com.zhangmen</groupId>
          <artifactId>solar-framework-starter-service</artifactId>
          <version>${solar.version}</version>
      </dependency>

      网关:

      <dependency>
          <groupId>com.zhangmen</groupId>
          <artifactId>solar-framework-starter-zuul</artifactId>
          <version>${solar.version}</version>
      </dependency>
      • 入口类添加注解

      @EnableSolarService , @EnableSolarZuul 封装了标准 Spring Boot / Spring Cloud / Apollo 等大量注解,降低业务的使用成本。

      微服务:

      @EnableSolarService
      public class DemoApplication {
          public static void main(String[] args) {
              new SpringApplicationBuilder(DemoApplication.class).run(args);
          }
      }

      网关:

      @EnableSolarZuul
      public class DemoApplication {
          public static void main(String[] args) {
              new SpringApplicationBuilder(DemoApplication.class).run(args);
          }
      }

      8)Solar Nacos SDK 和 Solar Eureka SDK 升级和回滚

      升级和回滚方案非常简单,此方式同时适用于网关和服务,见下图:

      36.png

      作者信息

      吴毅挺,掌门技术副总裁,负责技术中台和少儿技术团队。曾就职于百度、eBay 、携程,曾任携程高级研发总监,负责从零打造携程私有云、容器云、桌面云和 PaaS 平台。

      任浩军,掌门基础架构部负责人。曾就职于平安银行、万达、惠普,曾负责平安银行平台架构部 PaaS 平台 Halo 基础服务框架研发。10 多年开源经历,Github ID:@HaojunRen,Nepxion 开源社区创始人,Nacos Group Member,Spring Cloud Alibaba & Nacos & Sentinel & OpenTracing Committer。

      参与 Nacos 落地的基础架构部成员,包括:童子龙,张彬彬,廖梦鸽,张金星,胡振建,谢璐,谢庆芳,伊安娜

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      CNCF 新增两个孵化项目 | 云原生生态周报 Vol. 58 Fri, 20 Jun 2025 02:20:33 +0800 7.24次条.png

      作者 | 丁海洋、孙健波

      业界要闻

      1. SUSE 计划收购 Rancher Labs

      SUSE 计划收购 RANCHER LABS,旨在成为企业 Kubernetes 管理市场的领导者。

      1. CNCF 计划发布新的与 K8s 安全相关的认证项目

      该项目为 CKS(Certified Kubernetes Security Specialist),暂且翻译为“认证 K8s 安全专家”。

      1. Operator Frameworks 进入 CNCF 孵化项目

      Operator Frameworks 被 CNCF TOC 认可,成为新的孵化项目,包括 Operator SDK 和 Operator Lifecycle Manager 两部分。

      1. Contour 进入 CNCF 孵化项目

      这是一个高性能的 ingress controller,为 Envoy 提供控制面。

      1. 两个 CVE 漏洞:

      上游重要进展

      1. Service Account Token for CSI Driver

      主要是提出了一种为 CSI 通过挂载 volume 获取的 service account 的一种方式。

      开源项目推荐

      1. K8Spin

      在 K8s 上构建多租能力的项目,现在已经开源。

      本周阅读推荐

      1. 《Spark on K8s 的最佳实践和需要注意的坑》

      近年来,K8s 在业界越来越流行,由于其有很多优点,很多企业将应用部署到 K8s 中,Spark 从 2.3 版本开始支持使用 K8s 作为资源管理器。本文介绍了在 K8s 上运行 Spark 作业的最佳实践和需要注意的坑。

      1. 《Diving Into Istio 1.6 Certificate Rotation》

      本文分析了 Istio 1.6 中的证书轮转。

      1. 《Presslabs is the First Managed WordPress Hosting Platform running on Kubernetes》

      第一个运行在 K8s 上的 Managed WordPress Hosting Platfor。

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      3. 懂了这些,方敢在简历上说会用Jackson写JSON Fri, 20 Jun 2025 02:20:33 +0800

      你必须非常努力,才能看起来毫不费力。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

      前言

      各位好,我是A哥(YourBatman)。上篇文章:2. 妈呀,Jackson原来是这样写JSON的 知道了Jackson写JSON的姿势,切实感受了一把ObjectMapper原来是这样完成序列化的...本文继续深入讨论JsonGenerator写JSON的细节。

      先闲聊几句题外话哈。我们在书写简历的时候,都会用一定篇幅展示自己的技能点(亮点),就像这样:

      这一part非常重要,它决定了面试官是否有跟你聊的兴趣,决定了你是否能在浩如烟海的简历中够脱颖而出。如何做到差异性?在当下如此发达的信息社会里,信息的获取唾手可得,所以在知识的广度方面,我认为人与人之间的差异其实并不大:

      你知道DDD领域驱动、读过架构整洁之道、知道六边形架构、知道DevOps......难道你还在想凭一些概念卖钱?拉出差距?
      你在用Spring技术栈、在用Redis、在用ElasticSearch......难道你还以为现在像10年前一样,会用就能加分?

      一聊就会,一问就退,一写就废。这是很多公司程序员的真实写照,基/中层管理者尤甚。早早的和技术渐行渐远,导致裁员潮到来时很容易获得一张“飞机票”,年纪越大,焦虑感越强。

      在你的公司是否有过这种场景:四五个人指挥一个人干活。对,就像这样:


      扎不扎心,老铁]]> SpringCloud 应用在 Kubernetes 上的最佳实践 —— 诊断(线上联调) Fri, 20 Jun 2025 02:20:33 +0800 前言

      当云上的应用行为不符合预期的时候,您会怎么处理呢?修改代码,打包,部署,然后查看日志?或者开远程调试端口远程调试?

      这些步骤都比较繁琐。现在EDAS提供了端云联调的工具,让您在本地就可以启动应用并且能跟云端服务联调。只需三个步骤,您就可以在本地获得跟云端服务联调的能力,下面我们一起来体验吧!

      打开调试开关

      默认情况下,EDAS端云联调功能是关闭的,只有打开命名空间中的调试开关后,本地服务才能跟云端联调。您可以只对开发环境的命名空间开启端云联调,而对其他环境保持关闭,这样既方便本地开发,也保证其他环境服务稳定。

      EDAS命名空间的默认关闭状态如下所示,打开开关即可启用此功能:
      1.jpeg

      准备可远程访问的节点

      EDAS 端云联调只需一个可远程 SSH 的Kubernetes集群节点即可,如果您已具备这样的节点可跳过此节,否则可参考如下步骤来进行配置。

      在 Kubernetes 集群内任意选择一个机器节点,进入 ECS 实例详情,绑定一个弹性公网 IP:
      2.png

      绑定弹性公网IP后,需要设置实例安全组规则以放通 SSH 端口的流量。进入实例安全组,设置入方向规则允许访问 SSH 的22端口:
      3.png

      上图中的授权对象0.0.0.0/0表示22端口对公网开放,您可以根据本地网络的公网出口IP来设置授权对象,只允许您所在的网络访问实例的22端口,进一步提升安全系数。

      最后,设置实例登录密码并重启即可完成 SSH 配置:
      4.png

      配置插件

      现在我们来配置 IDE 插件来启用端云联调。此功能支持 Intellij IDEA 和 Eclipse 两种 IDE ,配置流程相同,下面以 Intellij IDEA 来说明配置参数。

      打开 IDE 配置页面,选择 Alibaba Cloud Toolkit -> EDAS 页面,勾选 Join EDAS Registry 选项,如下所示:
      5.png

      参数说明如下:

      • Region:您的Kubernetes集群及服务所在区域。
      • Namespace:EDAS命名空间。如未看到所需的命名空间,请参考上面的《打开调试开关》小节打开端云联调功能。
      • Gateway Host:可远程 SSH 的节点,请参考上面的《准备可远程访问的节点》小节来配置。
      • Username:可远程 SSH 的账号名。这里可填入一个不存在的账号,插件会进行检测,如不存在则进行创建。
      • Password:可远程 SSH 的账号密码。如果账号不存在,则插件使用此密码来创建账号。
      • Server Port:您的Spring Cloud 项目服务端口。

      填入以上信息后,点击页面上的 Add SSH Rule 按钮进行配置,插件会提示授予 root 权限来进行代理配置:
      6.png

      授予 root 权限,点击 Add 完成配置。至此,您已完成全部的配置流程,团队成员可直接复用上述配置的 SSH 的节点、账号和密码,无需重新配置。

      启动应用进行联调

      现在我们跟平常一样启动本地应用,IDE 会提示当前处于端云联调状态:
      7.png

      在此状态下,本地服务能正常调用云端服务,云端服务也能调用本地服务。另外,您也可以使用 IDE 的调试模式来启动本地应用,端云联调同样生效。

      结语及后续

      在本篇中,我们介绍了EDAS端云联调工具,借助此工具本地服务能跟云端服务进行联调。端云联调对于可重现的问题来说是一个非常高效的解决方案,但不适用于如下问题:

      1、已发生的问题;
      2、不能稳定重现的问题;

      对于这样的问题,我们有更好用的解决方案:APM监控。通过APM监控,您可以方便的查看分析系统异常、服务异常、接口异常等各种异常信息,下一讲我们将详细介绍《APM监控》,欢迎订阅。

      ]]>
      Seata 1.3.0 发布 Fri, 20 Jun 2025 02:20:33 +0800 1.3.0 版本支持了像多主键,自动升降级等大量feature,性能得到大幅度提升,修复了旧版本的大量bug。

      此版本更新如下:

      feature:

      • [#2398] 支持 MySQL 多主键
      • [#2484] 支持 Redis 存储模式
      • [#2817] Saga 流程设计器 Groovy Script Task
      • [#2646] Server 支持 HikariCP 数据源
      • [#2253] 支持根据连续错误数动态升降级
      • [#2565] 支持事务注解类标注
      • [#2510] 协议新增 LZ4 压缩支持
      • [#2622] Server 支持版本检查
      • [#2658] 支持 Oracle 同一实例下不同用户的事务
      • [#2620] 支持使用 Nacos 注册中心配置 group 属性
      • [#2699] 支持 ACM 配置中心
      • [#2509] 支持 update 操作回滚所有数据列和更新列
      • [#2584] StateHandlerInterceptor 和 StateRouterInterceptor 支持 SPI
      • [#2808] Server 鉴权支持 SPI
      • [#2616] TCC 模式支持 Dubbo 和 Sofa-RPC 注解调用
      • [#2831] Saga 模式支持 jackson parser
      • [#2554] 增加 zookeeper 序列化支持
      • [#2708] 支持 array, datalink 等 JDBC 类型
      • [#2412] xid 生成支持雪花算法
      • [#2611] 支持配置缓存,去除配置中心强依赖

      bugfix:

      • [#2893] 修复 postgresql 表名中含 schema 取 tableMeta 错误的问题
      • [#2887] 修复 RM 接收 response 的逻辑
      • [#2610] Nacos 配置同步脚本加入Nacos权限属性控制
      • [#2588] 修复check style不通过时,无详细信息报出的问题
      • [#2543] 修复 ShutdownHook signal 无效问题
      • [#2598] 修复无法注册到 Nacos 的问题
      • [#2618] 修复 zookeeper 无法创建目录的问题
      • [#2628] 修复 delete 操作时表名加别名找不到表名问题
      • [#2639] 修复 Apollo 配置中心由于属性大小写导致的无法加载问题
      • [#2629] 修复 PostgreSQL 相同实例不同 currentSchema 导致的 resourceId 重复问题
      • [#2659] 修复 MySQL 使用 last_insert_id 获取到 undo_log id 问题
      • [#2670] 修复 Server dataSource 初始化多次的问题
      • [#2617] 修复类和方法上注解获取不正确的问题
      • [#2603] 修复无法获取 generated keys value 的问题
      • [#2725] 修复 insert 操作时主键前含有其他表达式导致的索引位置不正确的问题
      • [#2698] 修复嵌套 GlobalLock 被提前解绑的问题
      • [#2755] 修复 TCC 模式 branchCommit 和 branchRollback 抛出异常无返回值的问题
      • [#2777] 修复 rollback 重试次数设置为 0 无法回滚的问题
      • [#2812] 修复使用 shardingSphere & Seata 获取 PostgreSQL tableMeta错误的问题
      • [#2760] 修复回滚失败 failureHandler 无法抛出失败异常的问题
      • [#2837] 修复 SubStateMachineHandler 中错误的常量引用
      • [#2839] 修复 Saga 模式补偿成功业务异常丢失的问题
      • [#2650] 修复 TCC 和 Saga 模式在 AbstractConnectionProxy解析SQL的问题
      • [#2850] 修复 Saga 流程设计器导致浏览器崩溃的问题
      • [#2868] 修复找不到 AsyncEventBus 依赖的问题
      • [#2871] 修复获取 'schame'.'table' 类型 tableMeta 错误的问题
      • [#2685] 修复 Oracle insert 操作使用 sysdate 报错的问题.
      • [#2872] 修复 undo sql 中主键缺失转义符的问题
      • [#2875] 修复 ColumnUtils delEscape删除表名带 schema 转义符错误的问题.

      optimize:

      • [#2573] 在随机负载均衡中使用 ThreadLocalRandom 代替 Random
      • [#2540] 重构 RPC 处理方法名和接口
      • [#2642] 优化 SofaRegistryServiceImpl 线程不安全的 double check
      • [#2561] 获取 tableMeta 逻辑统一
      • [#2591] 支持 zookeeper sessionTimeout和 connectTimeout 默认值
      • [#2601] 优化 spring-boot-starter 包结构
      • [#2415] 按照分支事务类型决定数据库操作行为
      • [#2647] 移除无用的变量
      • [#2649] 优化获取 tableMeta 的逻辑
      • [#2652] 支持 consul 自定义服务端口
      • [#2660] 优化 IdWorker 包路径
      • [#2625] Mockito.verify 代替 Mockito.doAnswer
      • [#2666] 补充使用用户 logo
      • [#2680] 优化 GlobalTransactionalInterceptor 为单例
      • [#2683] 优化 TccActionInterceptor 的日志打印
      • [#2477] 重构 RPC 客户端请求处理
      • [#2280] 重构 InsertExecutor
      • [#2044] 优化 ColumnUtils.addEscape
      • [#2730] 优化 配置中心类型校验
      • [#2723] 优化 postgreSql 获取 tableMeta 的处理逻辑
      • [#2734] 优化 postgreSql 依赖的 scope
      • [#2749] 优化 logger class 错误问题
      • [#2751] 拷贝 jdbc driver 到 docker 镜像
      • [#2759] 优化线程池线程命名风格
      • [#2607] insert 操作检查 pk 表达式支持
      • [#2765] 优化 XA 对不支持的 resource 的逻辑处理
      • [#2771] 禁用不稳定的单元测试
      • [#2779] 方法变量 ConcurrentHashMap 替换为 HashMap
      • [#2486] 重构 RPC server 端的处理逻辑
      • [#2770] TCC confirm 和 cancel 支持 void 返回值
      • [#2788] 优化 server 日志格式和样式
      • [#2816] 优化实例的创建逻辑
      • [#2787] 优化雪花算法中的 workId
      • [#2776] 优化字符串拼接
      • [#2799] 优化操作符
      • [#2829] 升降级检查去除加锁和异步化
      • [#2842] 优化 sql 格式
      • [#2242] 优化 PreparedStatementProxy 初始化逻辑
      • [#2613] 优化 DTO 和 typo

      非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。

      同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。

      相关链接

      ]]>
      云原生高可用技术体系的构建 Fri, 20 Jun 2025 02:20:33 +0800 QzpcVXNlcnNc576O5LmfXEFwcERhdGFcUm9hbWluZ1xEaW5nVGFsa1wzODk0MTg4MTBfdjJcSW1hZ2VGaWxlc1wxNTk1MjEzNTY3MzkxXzg3MDA1MjdGLTE0MjItNDVlMy05ODE5LUI5NjVDMjJBODc3Ny5wbmc=.png
      伴随着互联网业务的高速发展,越来越多的线下场景需要转移到线上,而线上业务的量级也在飞速增长,给互联网业务的技术架构带来了严峻的挑战,原来的“一体机+数据库”的方式已经不适用于当前的主流业务,越来越来的业务开始向分布式架构和云原生架构演进。同时,原来单一的技术环境开始走向分布式、分层的多组件技术架构,越来越多的组件使得保障业务稳定运行的工作也越来越艰巨。

      一、容灾

      航空系统的容灾体系做得非常优秀。航空系统的容灾体系从人、飞机和环境三个维度来考虑,才能构建一套优秀的容灾方案。

      从人的维度,以防万分之一的紧急情况出现的可能,每年要进行多次的模拟机训练或者实景演练。一架飞机上都会配备至少两名飞行员,二者相互合作的同时也要相互监督。

      从飞机的维度,每一个航段前,光是一个绕机检查可能就有几十个项目需要检查。机检查是由地面机务人员和飞行机组分别完成,同样也是为了更仔细的检查,降低错误率。每架飞机还有短期全面检查和长期全面检查,飞机上的每一个设备都是独立的双系统在工作。

      从环境的维度,气象雷达可以让飞行员感知到几十甚至几百海里范围内的天气情况。飞机防撞系统可以让飞行导航显示仪上显示正在接近的可能存在威胁的飞机。盲降系统是由地面发射的两束无线电信号实现航向道和下滑道指引,飞机通过机载接收设备,进行降落。

      从航空业的容灾体系构建中我们可以发现,容灾的核心思想是基于隔离的冗余。在系统设计中,其实也经常用到冗余的机制,比如机器经常是多台、数据是多备份等。容灾的评价指标主要有两个:
      一是RPO(Recovery Point Objective),即数据恢复点目标,以时间为单位,即在灾难发生时,系统和数据必须恢复的时间点要求。
      二是RTO(Recovery Time Objective),即恢复时间目标,以时间为单位,即在灾难发生后,信息系统或业务功能从停止到必须恢复的时间要求。RTO标志着系统能够容忍的服务停止的最长时间,系统服务的紧迫性要求越高,RTO的值越小。

      1. 业界主流容灾方案

      如下图所示,业内主流的容灾方案最早是异地冷备的方式,后来演进到同城双活方式,最后发展成为“两地三中心”。
      image.png

      业界主流容灾方案

      2. 阿里AHAS

      阿里AHAS容灾方案使用的是比“两地三中心”更前沿的“异地多活”方案,在所有的数据中心都能提供服务的同时,RPO和RTO能做到分钟级甚至秒级。下图是阿里AHAS的产品形态。AHAS在2013年之后就开始大规模在阿里内部使用,并且作为高可用平台的一个核心模块,开始服务外部客户。AHAS通过异地多活,能够真正做到对于宏观架构的容灾,并抵御大规模的失败场景。比如一个城市的机房出了故障,可以很轻易地把流量实时切换到另外一个机房。
      image.png

      AHAS异地多活的产品形态

      二、容量

      在互联网业务下,流量的不确定性非常明显,经常会出现流量高峰事件,比如微博的热点、阿里的双11、12306的火车票放购等事件。在这种场景下,如何做好容量规划就变得至关重要。

      1. 压测

      传统的压力测试通常关注的是性能的好坏,这是一个相对模糊的概念,不需要很精准。但是在互联网的情况下, 我们需要精准地获取到一个系统的实时吞吐量,以便更好地应对突发事件。在这种情况下,压测要尽可能地模拟一个真实的环境,而不能像以往一样,在一个额外的环境去测试。压测时在流量规模、流量模型、系统环境上都需要一个尽可能真实的环境,这样才能精准做好容量规划。

      传统的压测工具虽然仍在发挥作用,但是随着互联网的发展,已经越来越不能去适应互联网技术的迭代。互联网的压测有几个明显的特点:
      强调流量的真实性;
      压测规模要足够大;
      必须简单易用,交互式压测。

      当下互联网压测已经变成了一个实时的产品,方便进行实时的调控。基于这样的背景,阿里构建了基于PTS的流量引擎,大家可以在阿里云上直接使用,其特点如下:
      流量真实。流量来源于全国上百城市,覆盖各运营商(可拓展至海外),真实模拟最终用户的流量来源,相应的报表、数据更接近用户真实体感;发现端到端更多更全面的问题,压测即是模拟考。
      压测规模强大,可支持3kW QPS。
      简单易用,门槛低。复杂场景的全可视化编排,支持自定义编排能力、指令、控制、函数等相关能力,覆盖95%以上的HTTP压测场景,和JMeter构建能力不相伯仲,同时免去复杂的理解学习成本;除了自身丰富的客户侧监控数据,还可集成云监控和ARMS监控。压测过程提供日志明细查询,配套有请求瀑布模型,压测之后的报告和问题定位更方便。结合AHAS可额外提供流量吞吐控制能力、容量水位管理、熔断降级保护功能。除了强大的自研功能,对于开源JMeter的支持也很友好,支持JMeter脚本转化为PTS压测,同样支持原生JMeter引擎进行压测。

      2. 全链路压测

      在实践中发现,单系统单应用的压测与真实场景之间的误差非常大,因为在压测的时候无法验证整个系统的方方面面,而且很多问题只有在真正的大流量场景下才会暴露,所以要进行全链路压测,其核心是希望未来的事件能够提前到在当前时间内发生,能够用最真实的场景来端对端的验证系统的能力和稳定性。

      为了实现更好地进行全链路压测,阿里云提出了基于PTS的全链路压测解决方案,其架构如下图所示。
      image.png

      基于PTS的全链路压测

      从压测环境、压测基础数据、压测流量(模型、数据)、流量发起和问题定位对基于PTS的全链路压测解决方案总结如下:
      压测环境:对应真实的线上环境,压测结果和问题暴露都是最真实的情况,可通过压测流量全局识别、透传(影子表),或者等比隔离环境,或复用生产库(压测使用测试数据),业务挡板。
      压测基础数据:构造满足大促场景的核心基础相关数据(如买家、卖家、商品信息),以线上数据为数据源,进行采样、过滤和脱敏,保持和线上一个量级。
      压测流量(模型、数据):链路范围、访问量级、参数集合、基础数据的特性一起构造压测的业务模型,和真实业务情况保持一致。
      流量发起:模拟全国各地真实的用户请求访问,探测站点能力。
      问题定位:发起侧多维度的监控和报表,服务端可通过其他生态产品协助定位。

      三、线上防护

      线上防护对于系统高可用来说是一个非常重要的环节。随着分布式技术的应用,节点越来越多,技术越来越复杂,出错的机会也相对增大。同时,在互联网的条件下,业务的发布也越来越频繁。在互联网的环境下,系统随时面临一些不确定事件、流量冲击等,不能奢望每次出现故障的时候都有人工来干预,而是需要系统自身有一定的防护能力,能够让自身在任何环境下都能有最佳的工作状态。

      1. AHAS流量防护

      阿里云将流量防护广泛应用于各种场景,比如双11峰值流量、秒杀活动、物流、订单处理、商品查询、付款等。同时,阿里云也成功地将流量防护能力融合到了云产品AHAS(Application High Availability Service,应用高可用服务)中。AHAS涵盖了阿里巴巴多年来在应用高可用服务领域的技术沉淀,包括架构感知、流量防护、故障演练和功能开关四大独立的功能模块。如下图所示,AHAS构建了一个从入口到最后端的一个完整的防护体系。
      image.png

      AHAS经典流量防护布局

      2. AHAS针对大流量场景的保护措施

      流量防护首先需要考虑的是对大流量场景的保护,比如url、服务提供方、重点业务等,突然出现超乎预期的大流量,基于AHAS可以做如下防护措施:
      (1)如果有性能压测,可以精准设置QPS阈值。有了QPS阈值,可以用来限流,避免出现超负载的流量。
      (2)如果没有性能压测,也可以通过秒级监控,实时设置阈值。
      (3)支持高阶功能:流控模式支持直接、关联、链路,流控方式支持快速失败、Warm UP、排队等待。

      3. AHAS针对不同场景的措施——异常隔离

      在特定未知的场景下,可能出现不稳定的因素,如慢SQL,甚至死锁,导致整个应用越来越慢,甚至整个应用没有响应,这时候要对异常流量进行隔离,以免影响到正常的流量。

      4. AHAS针对不同场景的措施——系统防护

      在某些场景下,比如系统的负载CPU飙升,系统没有反应,无法确认具体哪个接口导致问题的出现,这时AHAS提供了一个终极大招:系统保护。系统保护就是当系统负载比较高的时候,会自动根据入口流量和系统的负载取得一个动态的平衡,保证系统不会恶化的同时,也能处理最大的入口请求。在这种情况下,系统对各种流量都是平等的,无法设置流量的优先级。

      四、演练

      很多故障是一个小概率事件,但是一旦发生,所造成的损失是不可估量的,比如巴黎圣母院的火灾。互联网业务也是一样,小概率的故障也可能带来不可挽回的经济损失,甚至是法律风险。因此,故障演练是一个完备的容灾体系所必需进行的一步。

      1. 企业为什么需要做故障演练?

      如果一个业务系统的流量很小且趋于稳定,就没有必要进行故障演练。但是如果一个企业处于高速发展中,业务发展快,有大量的稳定性技术债,其业务系统不断的变化,甚至今天的形态跟昨天的形态都不一致,架构也日益复杂,那么故障演练就是十分必要且必需的。因为每个环节的不确定因子都是累积的,如果不进行故障演练,最后一旦发生故障,极大可能会对系统造成严重破坏。故障演练还可以培养企业人员的故障处理经验,增强人员的应急能力。

      2. 企业引入故障演练遇到的常见问题

      在企业进行故障演练的时候,经常会遇到一些问题,比如如何设计组织架构,如何选择技术方案,如何落地演练实践等。如果业务牵涉到资金,就要做一个清晰化的深层评估,不要因为演练导致出现资金上的亏损,比如在演练中用到的收费内容(例如短信等)要考虑周全。

      3. 阿里的故障演练方案

      如下图所示,阿里有一套完整的故障演练方案。一开始是通过一些工具或者脚本,在2016年之后才开始将通用的故障模式沉淀为系统。2018年阿里将内部沉淀多年的实践正式在阿里云商用,2019年将沉淀多年的故障注入场景正式开源,成为国内首个混沌工程开源产品。
      image.png

      阿里故障演练历程

      4. AHAS故障演练

      AHAS故障演练的产品架构如下图所示,其定位是一款简单、安全、低成本的故障演练工具,能够帮助用户快速实施演练并发现问题。

      ]]>
      我们找阿里云资深技术专家李响聊了聊开源和云原生 Fri, 20 Jun 2025 02:20:33 +0800 李响是 CoreOS 最早期的工程师之一,参与创建了 etcd、operator framework、rkt 等开源项目。而在开源社区中,李响作为 etcd 作者被开发者所熟知,etcd 是国际知名且被最为广泛使用的分布式一致性存储系统,被阿里巴巴、腾讯、华为、腾讯、微软、谷歌、VMWare 等企业在生产环境和客户产品中使用,用来解决分布式系统中重要元信息存储、管理和备份的问题,以及分布式系统组件一致性协调的问题。

      在加入阿里云后,李响一直在推动云原生领域自动化运维相关理念、Operator概念、OAM 标准的建立。Operator 给予开发和运维人员在云原生平台构建无状态和复杂应用运维的理论标准和实践基础,大幅度提高了云原生运维平台的覆盖度,在开源生态中涌现出了超过500个 Operator 具体实现,覆盖了几乎所有的主流云原生软件的运维,其中包含 RocketMQ、Kafka、ZooKeeper、Consul、Argo、Kubeflow等。这些理念深度影响了云原生领域的发展。

      对于国内外开源的进展以及云原生实践的发展,我们找李响聊了聊他的看法。

      一、开源,从“使用”到“融入”

      开源在近些年的热度一直居高不下。所谓“开源”(Open Source),就是将软件的源代码公开,允许所社区成员对其进行修正、改进和创新,并将其成果与社区内的所有成员共享。除了开发者以个人的身份参与开源之外,企业也在加大力度参与开源软件的研发。

      李响提到,开源的融入应该是一个循环:使用 - 发现问题/做了新功能 - 提交代码给项目 - 更多人用。

      目前国内开源的发展更多停留在使用阶段,在合适的场景使用开源技术来解决一些业务问题。一个比较好的趋势是,国内对于开源技术的认可度逐渐提高,参与开源的企业和开发者也逐年增加。阿里巴巴也在积极推动一些先进的开源理念的落地,比如云原生。李响提到,当一些思想前卫的开发者愿意花时间与精力去实践云原生,并不断与国外的技术思想去碰撞,那么我们就能够对云原生社区的发展产生一些影响力,进而去引领云原生技术的发展方向。“其实中国的开发者完全有能力参与到开源项目的进程中,并且去影响开源项目。”

      还有一种趋势是,国内一些基于开源技术去构建技术体系的厂商越来越多。这些厂商不单单是像以前一样售卖开源技术,而是自己构建或者尝试去构建开源生态,并在国内或国外去推广。以前在OpenStack刚出来的时候,有很多厂商基于OpenStack这项技术进行包装、集成、售卖。国内一些to B的厂商也采用了相似的思路,比如发展较好的PingCAP。此外,一些初创公司开始去打造开源的技术和推广开源技术,这也是国内对于开源态度的一个转变。

      当然,国内开源的发展还有一些需要提升的地方。李响提到,一方面,国内的开发者应该更积极地参与到整个开源社区的建设中,不仅仅是技术建设,有时候思想碰撞也很重要。我们要做的不仅是给开源项目提一些短期的问题,或者帮助开源项目修复Bug,而是要为更长线的工作做准备。只有融入到社区中,才能给到社区更具体明确的需求,帮助开源社区的发展,甚至影响开源社区的未来方向。

      对于参与开源的企业而言,不论是创业公司,还是云厂商,能够从0到1去做一些开源项目,并尝试去做一些创新性的、先进性的项目,把国内在开源社区和先进生产力上的影响力发扬光大,甚至去打造一些国际知名的开源品牌,才能真正融入到开源的发展进程中。

      当然,开源的世界里也有一套自己的玩法。

      二、关于开源治理

      前段时间,谷歌将Istio项目商标的所有权正式移交至Open Usage Commons(OUC)。接受Istio后,OUC将与项目指导委员会共同制定商标使用指南,方便社区统一使用Istio项目商标。此举引发了业界激烈的讨论,作为CNCF TOC,李响谈了谈他对这一事件的理解。

      开源可以从三个部分来理解,第一部分是代码的开放,就是让大家可以看到并且修改代码。这是任何一家公司开源项目要做的最基础的事情。第二部分是开源工作中涉及的品牌和专利,把这部分工作开放,让品牌从属于一个中立的组织,这样其他厂商或者用户使用的时候,不会受到专利的限制,也没有品牌的担忧。

      第三部分是治理模型的开放,治理模型的开放意味着每一个项目都有一个治理组织,对开源项目做出一定贡献或者达到某种标准,就被允许加入到治理组织中,对开源项目的未来发展拥有一定的话语权和决策权。举个例子,阿里巴巴今天开源了一个项目X,最开始参与投票的5个人都来自于阿里巴巴,假设有一天阿里巴巴的贡献减少了,B公司的贡献增加了,那么B公司就有权利去推动这个开源项目的治理,进而控制它的走向。比如Redis项目近期放弃之前的专制管理模式,转而采用新的“社区自治模式”。这意味着 Redis 项目的未来命运将由整个社区决定,而不再单纯掌握在 Sanfilippo(Redis之父) 一个人手中。

      谷歌把品牌和专利移交给OUC,让品牌从属于一个中立的组织,就意味着在开放代码的基础之上,将代码相关的品牌和专利变成中立了,这样每一个参与项目的人都可以来使用Istio的品牌,人人平等。

      从开源项目的使用者来看,我们肯定希望开源的项目可以做到上面所说的三部分(代码、品牌、治理模型)都能够开放,从长期来看,这是对用户最有利的局面,这样开源项目就可以按照社区的需求导向来发展,而不是按照参与的某一家公司的意向来发展。

      但是谷歌并没有把治理模型开放出来,一是因为Istio项目还处于发展早期阶段,如果开放治理模型,会导致很多人参与到Istio早期进程中,分裂的话语权对于一个早期尚不成熟的项目而言是不利的。从商业化的角度来看,如果谷歌拥有对于开源项目的治理权利,那么,对于该项目的未来走向是有一定的把控能力,这对于谷歌未来布局产品线有一定的先发优势,这也是谷歌没有开放治理模型的一层考虑。

      Istio 之所以成为“香饽饽”,因为它是目前 Service Mesh 社区最引人注目的开源项目,而 Service Mesh 也是当下云原生领域最被看好的发展趋势。

      三,云原生:企业的顾虑与不可抵挡的行业趋势

      近年来,云原生的理念越来越受到业界的重视,李响作为云原生领域的创新者,也在推动国内云原生的技术布道,包括深度参与阿里云原生架构白皮书的撰写、参与设计云原生实践课程等。云原生理念对于国内企业而言,还处于一个早期发展的阶段,无论是技术、产品、标准等,都还处于快速迭代的过程中。企业在应用云原生的技术和产品时,难免有所顾虑。

      Q:国内外云原生的发展,有多大的差距?
      李响:总体来看,国内外云原生技术理念的发展没有太大的差距。但是由于云原生理念的兴起是在北美,所以社区生态的发展中心还是在硅谷这里,一些新的技术理念或者架构应用、创新场景等,也是需要一些时间才能慢慢引入到国内。

      区别比较大的是国内企业跟国外企业对于云计算的接受度。我们提到云,最先想到的一定是节约资源、弹性、成本降低、技术红利这些大家关心的点。北美企业的人力成本普遍较高,所以他们会愿意为云上的软件服务付费,尽可能节约人力成本。国内很多企业不缺少研发人员,整体研发能力也很强,在云服务提供附加值有限的情况下很多企业会自己研发一些定制化的能力。

      阿里是国内较早开始践行云原生理念的公司之一,通过在内部实践成熟之后,开始对外影响更多的企业。我们非常看重如何通过云原生为企业带来更多的附加值,并且这个附加值一定要超过企业自己定制开发带来的价值,只有这样,企业才会愿意拥抱云原生。未来阿里也会研发出一些更有竞争力的产品,提供更具价值的服务,并去影响海外的用户,让国内的云原生发展与国际接轨。

      Q:云上的服务未来会以什么样的形态存在?
      李响:未来的云一定不是资源,对于云厂商而言,重心不是售卖这些基础的资源,而是在云上建立一个服务体系和生态,让企业更便捷和方便的使用云上的服务。随着云服务规模化的发展,一个云服务可以交付给很多用户,由于边际效应,每个云服务的成本会大幅降低,对于企业而言更划算。但这对于阿里云而言也提出了一个挑战,如何把云服务更好地规模化,让每个服务更精细也更通用化,进而帮助企业解决更多常见的问题。

      Q:选用云原生技术时,企业对云原生技术栈进行大规模应用时的安全性、可靠性、性能、连续性存在疑虑。这个问题如何解决?
      李响:对于云原生有顾虑其实可以理解,毕竟云原生还处于早期发展的阶段。阿里云在帮助国内企业了解云原生、使用云原生上做了很多工作。对于云原生技术栈的可靠性、性能、连续性这方面的顾虑,我们从两个方面来解决,一方面是在内部尝试去使用这些技术,阿里巴巴内部有非常丰富的、大规模的使用场景,通过这些场景可以打磨云原生技术。在技术成熟以后,我们将这些技术回馈到社区,帮助云原生社区提高技术质量和发展水平。

      另一方面,阿里云上提供了丰富的云原生产品服务、云原生产品家族。以前一家企业想使用云原生的技术或产品,需要花费大量的精力研究一些开源项目,自己做运维和管理,还需要考虑集成、稳定性保障等问题,这样才能建立一个云原生平台。今天,为了方便企业和开发者更容易地使用云原生的技术和产品,更好地接受云原生的理念,并解决企业担忧的可靠性、性能、连续性等问题,阿里云大家了一整套云原生产品家族,提供了非常强的SLA保障。

      另外,关于云原生的安全性问题,2019 年,阿里云安全沙箱技术商用上线,支持 ECI、ACK、SAE 边缘计算等多种业务。蚂蚁金服也收购Hyper ,共同打造安全可靠的容器运行环境。阿里云安全沙箱是基于 MicroVM 构建的安全容器 Runtime。首先它是一个基于硬件虚拟化技术的 MicroVM,采用了深度定制优化 hypervisor,极简的虚拟机设备模型。其次阿里云安全沙箱也是一个容器 Runtime,提供镜像分发、镜像管理、容器网络、容器存储,完全兼容 OCI 和 CRI 规范。此外基于软硬一体设计的机密计算容器开始展露头角,阿里云和蚂蚁团队共同推出了面向机密计算场景的开源容器运行时技术栈inclavare-containers 。它基于Intel SGX等机密计算技术,支持蚂蚁的Occlum,开源社区的Graphene Libary OS,极大降低了机密计算应用的开发、交付和管理。

      Q:越来越多的厂商开始探索OAM落地实践,OAM有什么魅力?
      李响:OAM(开放应用模型)是一套由阿里云和微软共同发起、由云原生社区共同维护的应用描述规范(spec)。OAM 的核心理念是:“以应用为中心”,它强调研发和运维围绕着一组声明式的、灵活可扩展的上层抽象进行协作,而不是直接去使用复杂晦涩的基础设施层 API。

      在 OAM 规范下,研发和运维的关注点是完全分离开的。研发与运维只需要编写非常少量的、跟自己相关的一些字段,而不是完整的 K8s Deployment 和 HPA 对象,就可以轻松的定义和发布应用。这就是“上层抽象”为我们带来的好处。

      目前,阿里云 EDAS 服务已经成为了业界第一款基于 OAM 构建的生产级应用管理平台,并很快推出下一代“以应用为中心”的产品体验;在 CNCF 社区,知名的跨云应用管理与交付平台 Crossplane 项目也已经成为了 OAM 规范的重要采纳者和维护者。

      实际上,不止 AWS Fargate,我们云计算生态里的所有 Serverless 服务都可以很容易的使用 OAM 来作为面向开发者的表现层和应用定义,从而实现对原本复杂的基础设施 API 进行简化和抽象,将原本复杂的流程化操作“一键”升级为 Kubernetes 风格的声明式应用管理方式。

      更重要的是,得益于 OAM 的高可扩展性,你不仅可以在 Fargate 上部署容器应用,你还可以用 OAM 来描述 Function,虚拟机,WebAssembly 乃至任何你能想到的工作负载类型,然后把它们轻松的部署在 Serverless 服务上,甚至在不同的云服务之间无缝迁移。

      Q:未来,云原生会如何发展?
      李响:我们一直希望云原生产品生态可以做到标准和开放。不同厂商之间的服务、基础设施等,都可以实现互通。用户开发的应用可以运行在阿里云容器之上,也可以运行在其他厂商的容器之上,应用所依赖的东西本质上都可以是一些开放的接口。这其实一直是阿里云在努力的方向,从云原生的发展角度,我们也在尽量做到标准开放,并且跟社区生态比较好的进行融合,从而减少用户的担忧。

      阿里在云原生领域比较关注4个方面的发展:

      一是对于Kubernetes和Containerd的贡献。阿里在内部进行了大规模的实践,在性能、规模性、效率上与上游进行共建。从整个生态来看,阿里是最早开始布局云原生的厂商之一,也在尝试扩大云原生覆盖的范围,做边界的拓展,阿里云推出开源项目 OpenYurt,一方面是把阿里云在云原生边缘计算领域的经验回馈给开源社区,另一方面也希望加速云计算向边缘延伸的进程,并和社区共同探讨未来云原生边缘计算架构的统一标准。

      二是在微服务体系中,阿里有比较深厚的积累,同时也做了很多开源方面的工作。通过一些开源项目,比如Dubbo、Nacos等,把阿里微服务体系中的经验和实践向外输出。另外,阿里也在把微服务体系、开源体系的技术与云进行整合,这样使用云的客户就能更方面地直接使用开源的产品。

      三是推进微服务体系向下一代去演进,尤其在云原生领域,大家比较看好Service Mesh 的发展,阿里也在推动微服务体系与Service Mesh 融合,实现更好的兼容性和互通性,进而提高Service Mesh 整个生态的活跃度和成熟度。同时,我们也在一些开源项目和云产品变得更加云原生化或者说更加适合云的应用场景,比如消息系统 RocketMQ,我们正在提高RocketMQ开源软件自身的弹性和进行 Serverless 化,从而降低使用成本,并且在云上更容易部署。

      四是尝试一些创新的、先进性的云原生探索,比如OAM开放应用模型的 Kubernetes 标准实现 Crossplane 项目,在同 OAM 社区进行深度合作之后,今天的 Crossplane 是一个面向混合云场景的应用与云服务管理控制平面,它致力于基于 K8s 声明式 API,遵循开放应用模型标准对应用进行管理与交付,并通过独有的机制对云服务以云平台无关的、最终用户友好的方式进行抽象与管理。Crossplane 项目进入 CNCF Sandbox 也意味着,从今天开始 OAM Kubernetes 标准实现的所有代码、文档和整个 Crossplane 项目本身的所有权,都将转交给 CNCF 社区进行托管,与该项目背后的任何商业公司(无论是阿里云还是微软云)完成解耦。

      ]]>
      是他们,让云原生落地千万家企业! Fri, 20 Jun 2025 02:20:33 +0800 1999年,「伯俊」正式成立,从成立之初就一直致力于零售相关的IT服务,并在零售行业有很深的积累,是规模最大、客户最多、口碑最好、技术最强的信息化建设服务商之一。2017年,伯俊软件与阿里云全线展开深度合作,并提出了“All in 阿里云”的发展战略,与阿里云合作研发了8款联合解决方案,助力数千家零售企业以更低成本向数字化方向推进。其中,伯俊软件已成功交付的20多家企业级互联网中台客户,100%基于阿里云。

      伯俊软件为百丽国际打造的数字化营销解决方案,正是基于与阿里云合作开发的CDP数字化营销解决方案,可满足零售企业在顾客运营全链路覆盖、多渠道消费者数字信息的高效的OneID解决方案、自动化的精准营销活动等方面的需求,以支持企业在不断变化的市场需求中,进行数字化、精准化的营销。目前,该项目一期已经上线,百丽国际旗下滔搏运动已开始运作CDP并获得了显著成效。

      在这些项目中,不是阿里云签了合同然后再分给合作伙伴,而是阿里云找到合作伙伴联合研发产品,并一起向市场推出产品,这种合作思路给予了合作伙伴更大的空间。

      「创云」一直以“帮助企业在云端创造更大价值”为使命,专注于公有云应用服务与互联网安全咨询领域,是华南地区第一家百分百专注在公有云场景的互联网综合解决方案服务商。累计服务覆盖企业客户过万家,年公有云销售业绩数亿元,是阿里云战略级(最高级别)分销伙伴,也是云原生的核心伙伴。

      创云设立了云原生专员,并荣获2020卓越云原生专员称号。在过去一年里,创云的销售和技术团队,共同见证了阿里云原生给企业带来的技术红利。面对中秋、国庆电商大促场景,创云很多电商公司客户,通过阿里云AHAS应用高可用服务、PTS性能压测服务,在创云技术团队的支持下,有效应对流量突增场景下的系统的正常运行,保证业务稳定。

      011_副本.jpg

      在云原生技术浪潮之下,阿里云原生合作伙伴计划在技术先进性和产品定位上做了重大升级,从中间件技术向云原生技术的转变,从中间件驱动的阿里多年双11技术实践到云原生技术驱动2019年阿里集团核心系统100%上云,这充分证明了阿里云原生技术、产品体系的成熟。阿里云也希望通过云原生普惠的技术和产品帮助合作伙伴释放技术生产力。除了阿里中间件产品外,本次“阿里云原生合作伙伴计划”也覆盖了容器、微服务、Serverless、函数计算等众多产品。

      上述两家伙伴,只是众多云原生优秀伙伴中的杰出代表,是我们众多客户成功故事背后一家家默默服务的伙伴,他们才是真正的云原生市场领舞者,如下面一张张照片,记录下中国云原生起航时代的生动画面和花样年华。

      为此,阿里云对过去一年云原生优秀合作伙伴企业和个人进行了颁奖,表彰他们在助力企业数字化转型和全面拥抱云计算过程中做出的贡献,同时,也激励更多的合作伙伴加入其中。

      image.png

      在阿里云“做强生态”的战略指引下,以产品和解决方案“被集成”为核心,以帮助合作伙伴自身核心竞争力的成长为目标,阿里云原生合作伙伴计划从原来分销伙伴转变为解决方案伙伴,通过云原生技术和产品帮助伙伴的产品和解决方案技术换代,架构升级,让伙伴更加聚焦于自身业务优势,更好的发挥“长板效应”。
      点击链接:阿里云生态合作伙伴,了解更多阿里云生态合作伙伴信息和申请流程。

      ]]>
      SpringCloud 应用在 Kubernetes 上的最佳实践 -- 线上发布(可监控) Fri, 20 Jun 2025 02:20:33 +0800 文:骐羽

      前言

      在应用发布上线的时候我们最担心的莫过于因为代码的bug引发业务的问题,虽然我们可以通过灰度的方式分批发布减小影响范围,但是如果能够在发布的过程中从实时监控中快速的发现问题进行回滚,那么就能缩短业务受影响的时间。因此我们可以看到灰度、监控、回滚是整个发布过程中不可或缺的三大利器,有了这三大利器后,我们能够做到随时发布,从而加快业务的迭代和上线速度。而监控作为基础设施的一个重要环节,是保障生产环境服务稳定不可或缺的一部分,目前EDAS提供了非常丰富的监控能力,下面我们从不同的场景来详细介绍一下这些监控能力。

      体系化监控能力搭建

      监控体系,最怕的就是有覆盖不到的地方,一个覆盖全面的监控应该是从基础设施到上层应用均有对应的手段去覆盖:

      • 首先,如果故障产生时,最先感知到的其实是业务的受损,如交易量下跌、登陆的 UV 下跌等等。
      • 而如果继续往下钻,如果业务集群很大的时候,我们最先需要定位到某一个服务或者某一台机器,这个过程如果没有相应的工具相佐犹如大海捞针,所以一个分布式链路级别的应用监控会是建设 Spring Cloud 应用的很好的配搭。
      • 等到我们找到了相应的服务要开始进行定位分析的时候,根据问题类型(是错是慢?)接下来需要开始分析 JVM、内存、CPU 等维度的指标。
      • 最后我们可能会发现这个问题是由于业务代码引起,也有可能由于基础设施引起,而在 K8S 中,Prometheus 目前是属于容器领域基础监控最厉害的军刀。

      1.png

      如上图所示,目前 EDAS 结合阿里云上的某些云产品,完全能够满足日常的运维的需要并帮忙业务开发的同学快速的定位线上问题。

      EDAS常规监控能力

      系统监控

      应用实例的基础监控信息:

      上图功能提供了以应用实例的维度来查看每个实例的监控信息,提供的JVM/CPU/Load/内存等的监控信息也是我们经常需要关注的,当发现内存占用高,并且有频繁的FullGCC的情况时,我们可以通过创建内存快照进行分析来快速定位问题。SQL分析的能力也能快速帮助我们定位到慢查询用来排查问题。

      应用服务监控

      应用服务接口监控信息:

      2.png

      这里提供了以接口维度的监控信息,可以详细的看到接口在最近一段时间的请求信息,这里重点介绍一下接口快照功能,通过接口快照我们可以看到该接口的请求耗时,以及请求的TraceId,根据这个TraceId我们可以详细的看到本次请求的调用链以及调用的方法栈。

      3.png
      这里提供了以接口维度的监控信息,可以详细的看到接口在最近一段时间的请求信息,这里重点介绍一下接口快照功能,通过接口快照我们可以看到该接口的请求耗时,以及请求的TraceId,根据这个TraceId我们可以详细的看到本次请求的调用链以及调用的方法栈。
      4.png

      5.png

      调用链路的追踪在分布式系统下是一个必不可少的工具,尤其是在排查上下游依赖中究竟是哪个系统拖慢了整个请求非常有用,在调用的方法栈中可以直观的追踪到调用出错的地方。

      应用业务监控

      在EDAS中我们支持应用自定义业务监控,这需要我们开启高级监控的能力。从业务的视角来衡量应用的性能和稳定性,可以通过自定义来采集业务信息,来实时展现业务指标,帮助业务进一步完善监控信息。详细的监控配置可以参考ARMS业务监控

      Prometheus监控

      监控产品的历史由来已久,但是随着云原生技术的持续火热,Prometheus 作为新生代的开源监控系统,慢慢成为了云原生体系的事实标准。而在EDAS中的高级监控产品ARMS已经全面对接开源Prometheus生态,支持类型丰富的组件监控,提供多种开箱即用的预置监控大盘,且提供全面托管的Prometheus服务,更多的详细内容可以参考ARMS Prometheus

      通过以上这些监控能力,可以大大缩短线上问题从发现到定位再到解决的时间,提高开发和运维人员排查和解决问题的效率。

      EDAS应用发布场景中的监控

      以阿里巴巴集团的经验举例子,超一半以上的大故障都是在发布过程中产生,EDAS 针对发布这一场景结合 Kubernetes 的能力做了结合,其中的精髓内容总结三个词:先发再看再发。通俗的解释就是可以利用 EDAS 中分批(灰度)发布能力,同时在发布视图中,确保相关的指标回归正常之后,再开始下一批发布了。

      目前EDAS能够提供在三个维度上的指标监控数据,用来判断发布是否正常,列举如下:

      应用业务指标

      目前EDAS以接口的维度提供了每个接口在发布前后的总的请求数对比以及请求该比例的图例,并且还能够详细的看到在发布前后该接口的错误数、响应时间以及单机的请求数对比,如下图所示:

      6.png

      通过上图,我们可以直观的看到,当我们发布后应用的接口请求是否正常,以此来判断是否会对业务产生影响。

      应用异常

      在发布的过程中,我们也需要时刻的关注在发布中是不是有新的异常产生,我们想要有地方能够看到异常信息,避免直接登录到机器上去看业务日志,我们的发布监控提供了日志聚合分析的能力,可以在发布的过程提供实时的异常日志分析展示,如下图所以:

      7.png

      系统指标

      在新的业务功能上线的时候,我们除了对业务本身的一些异常和指标进行关注外,还需要关注系统的指标,这关系到我们需要评估现有的机器是否能够支撑我们的所有流量,是否需要进行水平扩容来更好的支持业务,我们的发布监控系统同样集成了系统的监控的能力,为我们的发布过程来保驾护航,详细的监控如下图所示:

      8.png

      以上内容我们通过三个维度为大家展示了在整个发布的过程中EDAS为我们提供的完备的监控能力,通过这个能力可以让我们的每一次发布都能做到不慌不忙,心中有数,每一次发布都能平滑让业务进行升级。同时我们也提供了查看发布报告的功能,将发布监控信息形成了一份清晰的可视化分析报告供分享他人。

      后续及结语

      本章我们介绍了EDAS中提供的监控能力以及如何对EDAS Kubernetes集群上的Spring Cloud应用在发布的过程中如何看监控发现异常信息,但是如果出现异常了该怎么办呢?接下来的文章我们将继续介绍,当出现问题后我们如何对已经发布的应用进行快速的回滚。

      ]]>
      Timing App的Serverless实践案例 Fri, 20 Jun 2025 02:20:33 +0800 1、背景和挑战:

      作为广受好评的学习应用,Timing App 专注于帮助社区用户提升学习凝聚力,达成学习目标。目前已有超过 700 万人通过 Timing 进行高效学习。与传统在线学习应用不同,Timing app 提供了 Timing 自习室、图书馆学习、 视频打卡、学习日记、契约群、学习服务等多类具有社交性质的在线教育服务,帮助用户找到自己的学习节奏,找到 坚持学习的一万种理由。Timing 业务本身具有潮汐特性,用户访问主要集中在晚间和节假日。受疫情影响,春节期 间峰值流量暴增 4 倍,公司面临较大的运维成本压力。在用户、流量爆发式增长背景下,Timing App 不得不直面以 下四大痛点:

      (1)系统稳定性差。原有 PHP 单应用架构系统无法做到线性快速扩容,在业务高峰时段,系统问题频繁发生,严 重影响用户体验。
      (2)产品迭代缓慢。随着业务的高速发展,原有单体架构对于产品的迭代力不从心,没法快速响应研发需求。
      (3)资源使用浪费。由于业务具有非常强的流量潮夕特征,需要按照业务高峰阶段进行资源保有配置,造成资源 的浪费。
      (4)技术成本昂贵。以前的团队除了技术负责人及少数团队新成员外,基本缺乏微服务架构实战经验。想要实现 微服务改造,急需能够快速上手的平台支撑,需要最大限度降低底层 IaaS, 容器以及常用微服务套件的学习 成本。

      2、云原生解决方案:

      阿里云应用引擎 Serverless(SAE),基于 Serverless 架构,屏蔽了底层 IaaS 运维和 K8s 细节,区别 于 FaaS 形态的 Serverless 产品,用户无需修改编程模型,零代码改造就能直接使用。同时,完美结合 Spring Cloud/Duboo 等微服务架构,提供应用发布、管理和服务治理等应用全生命周期的服务,完美贴合 Timing 的技术 需求:极限弹性伸缩,应用生命周期灵活管理,完美支持主流微服务架构。
      下图是方案架构示意图。
      image.png

      3、方案的关键优势:

      (1)利用弹性伸缩,应对不确定突发流量。提供秒级自动弹性 & 定时弹性能力,帮助应用轻松应对大促峰值流量, 保证 SLA 的同时也节省机器保有成本。多适用于互联网、游戏、在线教育行业。 应用环境随需灵活启停,节约成本。提供了一键启停开发测试环境的能力,即开即用,节省成本,方便运维。 适用于对成本敏感、云上有多套环境但部分环境闲置率较高的企业型客户(不限行业)。
      (2)中小企业快速构建云上微服务应用。帮助用户屏蔽底层 IaaS 购买和运维细节、底层 K8s 细节,低门槛部署 微服务应用。适用于初创型 / 上升期的公司(不限行业),业务增长很快,对增长有较高预期,但人员配置 跟不上。
      (3)整体技术架构更为清晰,每个服务相互独立且职责明确。加之阿里云应用引擎 Serverless (SAE)加持, 让客户只关注在业务层,做好产品。

      ]]>
      【升级】7月24日、28日、29日阿里云域名交易系统维护公告 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【域名】【域名交易系统维护通知】

      维护时间:北京时间2020年7月24日00:00 - 02:00、7月28日00:00 - 02:00、7月29日00:00 - 02:00

      维护内容:阿里云域名交易系统将于上述时间对后台系统进行维护。

      维护影响:届时阿里云域名交易平台(mi.aliyun.com)、域名抢注平台(wanwang.aliyun.com/domain/reserve)和域名控制台内我是买家、我是卖家等相关功能,将会无法正常使用,在维护期间会对您造成的具体影响如下:

      一、7月24日 00:00-02:00:

      1.所有域名交易购买类的操作,例如一口价(万网)、一口价(优选)、带价PUSH、竞价域名、违约认购、保留词一口价、委托购买、议价域名、域名抢注、域名预释放、域名回购等,将无法正常进行,下单后会立即返回失败;

      2.所有域名交易发布类的操作,例如一口价(万网)、带价PUSH、竞价域名、议价域名、委托购买等,将无法正常进行,不能发布、下架和修改等;

      3.所有米店、域名展示页等操作将无法正常进行,不能发布、下架和修改等。

      二、7月28日00:00-02:00:

      域名抢注、万网预释放、万网预订等业务无法正常预订,下单后会立即返回失败。

      三、7月29日00:00-02:00:

      所有域名交易订单支付类的操作,例如一口价(万网)、一口价(优选)、带价PUSH、竞价域名、违约认购、保留词一口价、委托购买、议价域名、域名抢注、域名预释放、域名回购、闯入竞价等的订单支付,保证金支付类的操作和服务费支付类的操作等将无法正常进行。

      如果您需要购买或管理以上业务操作,建议您避开维护时间段,以免给您的业务造成影响。

      由此给您带来的不便,我们表示歉意,敬请谅解。

      ]]>
      【其他】8月5日轻量云服务器价格调整通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【轻量应用服务器】【规格/价格调整通知】

      为了更贴近客户实际业务场景,提升产品服务质量,轻量应用服务器(SAS)计划于8月5日对中国香港地域和海外地域进行套餐规格和价格的调整。

      8.5日前持有如上地域套餐的客户,续费和升级仍可按照原套餐规格及价格执行,8月5日起新购套餐需按照调整后规格及价格执行。

      ]]>
      【升级】7月22日.cc/.tv/.name注册局系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【域名】【注册局维护通知】

      维护时间:北京时间2020年7月22日 05:00 - 09:00

      维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

      维护影响:届时 .cc/.tv/.name 域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

      1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

      2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

      如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

      由此给您带来的不便,我们表示歉意,敬请谅解。


      ]]>
      【升级】7月8日至23日IDaaS应用身份服务升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【IDaaS应用身份服务】【升级通知】

      升级窗口:北京时间 2020年7月8日 00:00 - 7月23日 23:59

      升级内容:为了更好为客户提供最新的统一身份权限管理能力,IDaaS计划进行全量实例更新,更新后版本为 v1.7.0。

      升级影响:升级期间,正常情况下无影响,极端情况下可能会出现几分钟内的服务中断。如果您的服务请求常出现失败,请稍后重试。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】7月21日至31日表格存储(Tablestore)公共云服务升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【表格存储(Tablestore)】【升级通知】

      升级窗口:北京时间 2020年7月21日 22:00- 7月31日 22:00

      升级内容:感谢您一直以来对表格存储(Tablestore)的支持,为了进一步提高服务质量,表格存储计划于2020年7月21日至7月31日陆续对表格存储公共云节点、金融云节点进行热升级,具体升级计划如下:

      1)北京时间2020年7月21日 22:00 - 7月25日 22:00 期间对华东2(上海)、华东2(上海)金融云、华北2(北京)、华南1(深圳)及华南1(深圳)金融云地域的公共云售卖节点进行热升级。

      2) 北京时间2020年7月28日 22:00 - 7月31日 22:00 期间对华东1(杭州)、华北1(青岛)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)金融云及西南1(成都)地域的公共云售卖节点进行热升级。

      升级影响:升级期间,可能会对您造成如下影响:

      1)整个热升级期间,每张数据表下的单个数据分区会最多受到不超过2次的影响;

      2)每个数据分区受到的单次影响时间约为1秒钟;

      3)升级期间,访问表格存储服务延迟可能会略有升高以及极低的错误率,通过重试即可解决;

      4)升级期间数据表创建、删除和更新操可能会失败,如遇错误,可以重试解决;

      5)升级期间没有请求则不会造成影响。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】7月21日消息队列AMQP升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【消息队列AMQP】【升级通知】

      升级窗口:北京时间2020年7月21日(周二) 21:00 - 23:59

      升级内容:华北2(北京)、华北3(张家口)、华北5(呼和浩特)、华东1(杭州)、华东2(上海)、华南1(深圳)、中国香港等地域的消息队列AMQP服务升级。

      升级影响:升级期间消息队列AMQP相关服务及控制台访问可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【其他】7月22日数据总线DataHub商业化开通通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【数据总线DataHub】【商业化开通通知】

      数据总线DataHub产品已于2020年4月20日完成商业化。为了保证客户业务平稳过渡,对商业化后未开通付费的用户,阿里云仍在7月22日前正常提供服务。

      自7月22日日起,DataHub进行升级,未开通DataHub的用户将无法使用DataHub相关服务,为了保证您的业务正常运行,请未开通用户在此时间前完成开通,开通地址,点此进入。使用过程中,如有任何疑问,请通过工单联系我们。

      ]]>
      阿里云新老用户买一得三超级优惠活动 Fri, 20 Jun 2025 02:20:33 +0800 互联网行业都在以自己的方式助力企业复工开产。阿里云自然也不例外,率先降本增效实施1亿元补贴,助力各行企业实现线上办公,让更多企业实现业务线上转型。今年采购季格外热闹,【采购季】上云仅需¥223/3年。盘点本次活动有以下亮点:

      1、新客购爆款主机商品赠送存储oss和数据库MySQL,买一得三;

      2、新购满额就送淘宝现金红包,最高可拿400元;

      3、爆款产品低至1折,满减最高减12000元;

      4、云服务器ECS,云数据库MySQL,云虚拟主机,云解析DNS,云安全等几十款产品年底限时特惠。

      活动入口:https://www.aliyun.com/activity/daily/cloud?userCode=d4m00na3

      活动依然有分会场

      1、基础云产品分会场:云服务器限时1折、抢数据库超值9.9元起、组合购买更划算;

      2、云服务器ECS分会场:新用户低至0.6折起、企业云分期首月免息、免费试用12个月;

      3、数据库分会场:新用户限时1折、爆款产品低至3折、续费满返最高2000元;

      4、域名&知识产权分会场:工商注册9.9元起、代理记账百元豪礼、商标提货券270元/张;

      5、安全分会场:产品组合购最高可减100,000元、全场低至1.5折、等保套餐好礼大放送;

      6、企业应用分会场:定制建站买1年送半年;参与抽取苹果手机;全场产品冰点钜惠;

      7、IoT物联网分会场:冰点优惠5折起、智能监控99元起、云资源低至7折起;

      活动入口:https://www.aliyun.com/minisite/goods?userCode=d4m00na3

      阿里云代金券领取:

      阿里云代金券领取示意图

      每日精品秒杀:

      image.png

      阿里云爆款主机5折不限新老用户:

      image.png

      阿里云快速建站:

      image.png

      活动入口:https://www.aliyun.com/minisite/goods?userCode=d4m00na3

      ]]>
      阿里云新老用户买一得三超级优惠活动 Fri, 20 Jun 2025 02:20:33 +0800 互联网行业都在以自己的方式助力企业复工开产。阿里云自然也不例外,率先降本增效实施1亿元补贴,助力各行企业实现线上办公,让更多企业实现业务线上转型。今年采购季格外热闹,【采购季】上云仅需¥223/3年。盘点本次活动有以下亮点:

      1、新客购爆款主机商品赠送存储oss和数据库MySQL,买一得三;

      2、新购满额就送淘宝现金红包,最高可拿400元;

      3、爆款产品低至1折,满减最高减12000元;

      4、云服务器ECS,云数据库MySQL,云虚拟主机,云解析DNS,云安全等几十款产品年底限时特惠。

      活动入口:https://www.aliyun.com/activity/daily/cloud?userCode=d4m00na3

      活动依然有分会场

      1、基础云产品分会场:云服务器限时1折、抢数据库超值9.9元起、组合购买更划算;

      2、云服务器ECS分会场:新用户低至0.6折起、企业云分期首月免息、免费试用12个月;

      3、数据库分会场:新用户限时1折、爆款产品低至3折、续费满返最高2000元;

      4、域名&知识产权分会场:工商注册9.9元起、代理记账百元豪礼、商标提货券270元/张;

      5、安全分会场:产品组合购最高可减100,000元、全场低至1.5折、等保套餐好礼大放送;

      6、企业应用分会场:定制建站买1年送半年;参与抽取苹果手机;全场产品冰点钜惠;

      7、IoT物联网分会场:冰点优惠5折起、智能监控99元起、云资源低至7折起;

      活动入口:https://www.aliyun.com/minisite/goods?userCode=d4m00na3

      阿里云代金券领取:

      阿里云代金券领取示意图

      每日精品秒杀:

      image.png

      阿里云爆款主机5折不限新老用户:

      image.png

      阿里云快速建站:

      image.png

      活动入口:https://www.aliyun.com/minisite/goods?userCode=d4m00na3


      ]]>
      20大新兴物联网趋势 Fri, 20 Jun 2025 02:20:33 +0800

      在详细介绍物联网趋势之前,我们想谈谈物联网技术本身。您如何看待物联网?物联网(IoT)是指计算设备或其他数字机器相互关联并且可以通过网络在它们之间传输数据的系统(它不需要任何人与计算机或人与人之间的交互)。以下所讨论的物联网趋势将会让你感到惊讶,因为在技术领域将会发生翻天覆地的变化。


      20大新兴物联网趋势


      从工厂设备到儿童玩具,很多物联网趋势正在出现,我们特别挑选了20种物联网趋势,并描述了每种趋势的所有细节。


      1.大数据融合


      物联网不仅强调改变人们的生活方式和经营方式,还着眼于产生大量的数据。构建大数据平台通常是为了支持大规模存储的需求和进行调研,这是充分发挥物联网优势所必需的。这就是我们正在面临的、并将在不久的将来大规模看到的物联网新趋势。


      Big-Data-Convergence_副本.jpg

      物联网和大数据有着紧密的联系,现在,我们看到许多新的设备被选择来产生公平的数据共享。云被认为能够控制分析需求和存储;它在处理更大的数据时缺乏一些细节,因此物联网需要从现在开始处理它们的大数据连接。


      2.边缘计算的数据处理


      物联网的基本弱点是它将设备添加到网络防火墙之后。保护设备可能很容易,但是保护IoT设备需要做更多工作。我们需要将网络连接和连接到设备的软件应用程序之间的安全性结合起来。


      Data-Processing-with-Edge-Computing_副本.png

      物联网在处理数据的同时,以其成本效益和效率获得最大的成功。在自动驾驶汽车、智能交通灯等智能设备中,数据的快速处理显得尤为突出。边缘计算被认为是解决这一问题的物联网趋势。


      在速度和成本方面,边缘计算通常要优于云。我们都知道更快的处理意味着更低的延迟,而这正是Edge Computing所做的。边缘计算的数据处理将与云一起存在,以改善物联网。


      3.更大的消费者采用率


      在未来的十年里,你将看到物联网的巨大变化,以消费者为基础的物联网的转变将发生,消费型物联网的资金增长将下降,未来将是工业物联网基础设施和平台的一年。这种物联网趋势需要时间来发展。


      Greater-Consumer-Adoption_副本.jpg

      Veniam、BetterView和Swift Navigation等基于工业物联网的公司将建立物联网架构,它将解决保险、交通、农业或电信领域等方面的困难,资本支出将明显减少。


      4.智能家居需求将上升


      在过去,我们已经看到物联网应用随着智能家居技术的理念而激增,这种趋势将在短期内继续下去,以便家庭能够获得更多的互动。人们不会指挥设备,相反,设备会告诉房子里的人他们应该做什么。


      “Smart”-Home-Demand-Will-Rise_副本.png

      爆炸性增长将减少;今年,我们将看到稳定增长,因为当今的公司正面临安全和碎片化问题。智能家居物联网趋势是当前的热门话题。


      5.医疗行业拥抱物联网


      在看到零售商从与客户互动的能力中受益的情况下,医疗行业正在使用可穿戴设备,这些行业将面临稳定的发展。


      The-Healthcare-Industry-Embraces-IoT_副本.jpg

      你能想象所有的医疗设备都在使用云,并为智能系统存储图像吗?值得注意的是,通过这种方式,政府将从中获得最大利益。医疗领域的物联网趋势受到高度鼓励。


      6.用于数据安全的Auto-ML(机器学习)


      现在,我们看到开发人员将注意力集中在新的方法上,人们可以使用类似区块链的技术安全地共享数据。如今,许多工业公司将学会信任和接受机器学习模型预测,并将适应其操作,以通过模型输出防止意外停机。


      Auto-ML-Machine-Learning-for-Data-Security_副本.jpg

      机器学习模型训练将是高度自动化的,就像auto-ML工具集一样,它将变得更加流行。工业公司将会增加与Could相关的大量资本资产(3-5倍),因为这是物联网的顶级趋势之一。


      7.物联网巨大的增长即将到来


      与其他类型的技术相比,IoT设备通常会获取有关设备和用户的更多数据和信息,到2020年,全球IoT设备将达到约310亿。今天,我们将物联网设备视为报告和跟踪的主要部分。


      IoT-–-Massive-Growth-Coming_副本.png

      根据物联网技术的发展趋势,我们可以很容易地假设,物联网将得到最大的发展。物联网通常是获取或收集如此多的数据,并且有人工智能来提取必要的数据。不久,我们将看到物联网设备为行动而工作,它们也将帮助技术人员提供有见地的建议。


      8.区块链用于物联网安全


      很快,各种各样的企业家、金融和政府流程、消费者和工业企业将分散化、自治、自我修复和智能化。一些初创公司在IOTA的Tangle (IOTA是一种分布式账本,用于记录和执行物联网生态系统中设备间的交易)上建立自己的地盘,为企业开发模块和其他组件,而不需要支付SaaS和云计算的成本。


      Blockchain-for-IoT-Security_副本.jpg

      您应该准备好查看集中式的整体计算模型,这些模型分解为作业和微服务,这些作业和微服务将分发给分散的设备和机器。有一天,物联网将渗透到当今人们无法想到的治理,健康,交易,财务和其他领域。这类物联网技术趋势将为我们带来巨大的有效差异。


      9.更好的数据分析


      未来几年,最显著的趋势将会让我们大吃一惊,那就是互联世界和物联网之间的关系。现在,世界和物联网如何与人工智能结合,成为所有企业和个人的决策助手,这就是我们所期待的。


      Better-Data-Analytics_副本.jpg

      人工智能是一种可以快速识别趋势的机器学习系统。物联网技术趋势将带来更好的数据分析,并使其更易于维护。它还从大量数据中收集见解,以便为我们的生活做出更好的决策。


      10.智慧城市将成为主流


      过去,我们已经看到各州正在推出更多技术和传感器,以利用可访问数据收集工具的优势。不久,我们将看到IoT趋势,具有远见的城市将投资于开拓性的数据交换,这将提供私有和公共组织以及公民之间的数据访问和组合。


      Smart-Cities-to-Become-Mainstream_副本.jpg

      我们很快就会有机会看到智慧城市思维的转变。与响应性城市的物联网整合将缓解交通拥堵,开启可持续发展,并提高安全性。


      11.区块链作为高效后端


      尽管大规模采用、可扩展性、安全性和成本都非常关键;但您将看到许多技术难题阻碍了物联网的发展。大多数软件都是为收集大量数据而设计的,出售这些数据似乎更有利可图。


      Blockchain-as-Efficient-Backend_副本.jpg

      这里最重要的问题是后端用于运行物联网设备的集中式网络。因此,信任现在是一个更重要的问题。区块链作为物联网技术的重要发展趋势之一,对于正确的数据保存和保护是非常必要的。


      如果我们想在家里安装一系列智能设备,我们就必须解决客户的信任问题,因为这对他们来说是一个更大的威胁,而且这种威胁可能不仅仅局限于他们的身份。因此,我们可以认为“区块链作为有效后端”是明年物联网的新趋势之一。


      区块链是这方面的帮手,它分散了在一个地方保存大量数据的安全问题。它允许代币经济,这通常可以为向提供商出售数据和其他信息的用户创建一个市场(或选择保持公开)。


      12.零售体验的个性化


      物联网使当今的零售业供应链管理更加高效。借助于传感器和其他智能信标技术,定制购物体验变得更加舒适,人们可以更加精确地进行定制。


      根据新的变化,接下来几年的物联网趋势将使您的交易活动个性化。您可以从您最喜欢的商店获得经常购买的产品的折扣通知,您喜欢的商场的室内地图将引导您找到所需的确切商品!


      物联网技术趋势将确保更好地整合个性化零售体验,最终可以开创购物新时代。


      13.通过物联网促进预测性维护


      不久后,房屋将通知业主有关设备故障,管道泄漏,电气问题的信息,以便业主避免房屋维修灾难。在所有这些物联网趋势的帮助下,今天可以实现的飞机、工厂和汽车中传感器的更多功能将很快进入您的家中。


      针对物联网的这些预测技能,我们将看到承包商提供的家庭护理即服务。无论你是否在家,只要发现任何问题,他们都会立即采取行动。


      如今的家庭保险公司已经知道智能传感器和其他已连接设备的性质在不断变化,这极大地影响了物联网趋势。如今,在汽车上可以找到的传感器,就像在允许消费者降低价格和向消费者安装主动监测系统以避免损坏的过程中一样,可以允许房屋保险公司降低风险和其他支出。


      14.云计算:物联网的未来


      数据保护将成为最重要的安全趋势之一。拥有一个已经连接到互联网的设备在很多方面都是有害的,使用间谍软件可以很快得到你的个人信息。


      Cloud-Computing-The-Future-of-IoT_副本.jpg

      智能家居、自动驾驶汽车和可穿戴设备的基础设施也将保存黑客、非法入侵者等的记录。私人数据也有很多用途,云计算可以帮助构建物联网的未来。这些物联网趋势必须得到适当的重视。


      15.软件即服务(Software-as-a-Service)成为常态


      如果我们讨论物联网趋势,软件即服务被认为是市场的热门话题之一。在低进入成本的帮助下,SaaS正在迅速成为IT领域最受欢迎的公司,很快我们就会有机会看到这类公司的广泛支持。在所有这些物联网技术趋势中,软件即服务将使人们的生活比以往任何时候都更好。


      16.物联网安全意识和培训


      这个行业还处于起步阶段,需要安全培训。培训将包括基本知识,例如收益和风险之间的差异以及其他安全建议。


      IoT-Security-Awareness-and-Training_副本.jpg

      通过适当的培训,建立可靠的安全性,整合安全性,促进物联网的透明度。在开始之前,有意识的物联网安全培训是必不可少的。上述所有的物联网技术趋势也要求用户意识,以使人们在他们的区域内保持安全。


      17.创建用于集成的统一框架


      缺乏统一的物联网框架是物联网与行业合作面临的挑战。这些公司没有中心共享平台。我们之前提到的所有物联网趋势都包括统一框架,因为这是保持行业安全的唯一方法。


      区块链将有助于加速应用程序的采用过程,帮助改进和开发应用程序。这些应用程序具有高性能阈值,并且此过程也有助于维护通常需要的数据密集型过程。


      18.能源与资源管理


      能源管理依赖于更好地了解能源消耗。可以安装在配电面板上的产品可以监控家庭的能源消耗。所有这些物联网趋势都可以很容易地融入到资源管理中,这将使人们的生活更加舒适和容易。


      当能量阈值将超过时,可以添加推送通知以通过智能手机发送通知。还可以添加其他功能,例如室内温度管理,控制洒水器等。


      19.语音控制的转变:从移动平台到管理物联网生态系统


      如今,对话在向前推进,对动态安全环境的响应也在向第一线移动,人们很困惑,当第一线的开发者最好的网络安全计划失败了,他们会做什么。依赖于移动系统的物联网趋势将会发展,并转变为对物联网生态系统的管理。


      在这种情况下,监管将是实施的唯一途径,这一趋势将是监管机构和政府从欧盟,美国,中国或日本将开始认识到所有的威胁,并将开始发布可能的保护指令。


      20.保持警惕和结盟:物联网的进一步扩展


      目前,技术领导者、开发公司和企业都在致力于充实物联网技术的真实应用。对业务价值和成果的需求被放大,这将为最近形成的数字产品中嵌入的传感器数量的增加指明道路。


      根据所有这些物联网趋势,当今更多的行业和公司将把物联网看作是吸引消费者、发展品牌、改善用户体验的魔杖。


      Gartner也是这么认为的,因此他们坚持同样的信念,声称到2020年,95%的电子产品将实现物联网。因此,现在制造商希望扩大物联网设备的数量,不仅如此,他们还应该努力使设备更加全面和容易获得。


      总结


      这些物联网趋势将使人们的生活比以往任何时候都更安全、更容易、更舒适。


      原文链接 ]]>
      阿里云蚂蚁区块链服务助力企业大数据营销一体化 Fri, 20 Jun 2025 02:20:33 +0800 随着消费升级的趋势,消费者越来越关心买到商品是否是正品。所以,商家在打造的品牌的同时,防止假冒商品,防串货已经成为商品品牌建设的关键。

      现在市场上,一方面造假、知假、售假的行为屡禁不止;另一方面,优秀企业的商品被假冒,无法及时发现并确定假冒产品的来源,企业的正当利益也受到了损害。

      阿里云整合了蚂蚁区块链商品溯源服务系统,赋予每个商品唯一的追溯码,对商品进行一对一的绑定,打通了商品的上下游全流程的应用场景,同时更真实的展示商品的全部购买过程,实现数字化管理,数字化营销,同时做到了商品可追溯,可控化的管理系统。

      商品溯源服务系统为企业和用户解决了以下问题:

      image.png

      传统的溯源码存在被大量复制的风险,阿里云整合的蚂蚁区块链溯源服务,该系统利用区块链和物联网技术追踪记录有形商品或无形信息的流转链条,不可篡改的登记在区块链上,解决了信息流转不畅,信息缺乏透明度等行业问题,同时规避了传统溯源码容易被大量复制的问题。该系统优点:

      image.png

      阿里云上的蚂蚁区块链溯源系统具体的应用场景:

      image.png

      此系统在天猫品牌商家间被广泛使用,具体案例如下:

      image.png

      由此可见此系统不但可以实现商品防伪,同时具备溯源、防窜货、数据分析等多样化功能,由此提高企业管理效率,降低销售成本,引导供给端生产企业提升产能。

      ]]>
      Spark on ECI大数据分析 Fri, 20 Jun 2025 02:20:33 +0800

      云栖号最佳实践:【点击查看更多上云最佳实践
      这里有丰富的企业上云最佳实践,从典型场景入门,提供一系列项目实践方案,降低企业上云门槛的同时满足您的需求!

      场景描述

      Spark作为快速、通用的大规模数据处理平台,更多关注Spark Application的管理,底层实际资源调度和管理更多的是依靠外部平台的支持例如Mesos、YARN、Kubernetes等。借助阿里云的容器服务Kubernetes版(ACK)、弹性容器组实例(ECI)、文件存储HDFS或者对象存储OSS提供灵活弹性计算资源弹性可扩展、计算与存储分离架构、成本可控的Spark on ECI解决方案实践。

      解决问题

      • 计算资源弹性能力不足,计算资源成本管控能力欠缺
      • 集群资源调度能力和隔离能力不足
      • 计算与存储无法分离,大数据量分析时出现数据存储资源瓶颈
      • Spark submit方式提交分析作业参数支持有限等缺点

      产品列表

      • 容器服务Kubernetes版(ACK)
      • 弹性容器实例(ECI)
      • 文件存储HDFS
      • 对象存储OSS
      • 专有网络VPC
      • 容器镜像服务ACR

      image

      直达最佳实践 》》

      160@Spark on ECI大数据分析.png.png

      ]]>
      阿里云企业级云服务器和入门级云服务器实例差别很大吗 Fri, 20 Jun 2025 02:20:33 +0800 选择阿里云服务器的时候,一般来说,新手用户都是通过阿里云的各种促销活动去购买阿里云服务器,毕竟便宜很多,细心的用户会发现,促销活动中的云服务器实例基本上以企业级的计算型、通用型和入门级的突发性能t5、共享型s6为主。

      有些人就有疑问了?阿里云企业级云服务器和入门级云服务器实例差别很大吗?如何选择?本文就是为了解答这个问题写的,希望大家看了之后能够对阿里云服务器实例认识更清晰,快速找到自己需要的云服务器产品。

      一、名词解释
      实例:云服务器实例其实就是一个虚拟的计算环境,包含 CPU、内存、操作系统、带宽、磁盘等最基础的计算组件。用户可以将一个弹性云服务器实例简单理解为一个独立的虚拟机。ECS实例是云服务器最为核心的概念,其他的资源,比如磁盘、IP、镜像、快照等,只有与云服务器实例结合后才能使用。

      阿里云入门级云服务器:用户用的最多的是阿里云突发性能t5实例和共享型S6实例。起步机型是1核1G机型,主打低价市场,适合运行一些一些轻量应用,阿里云突发性能t5实例的CPU性能基线限制在20%性能,共享型S6实例则没有限制。

      下面是突发性能t5实例、共享型S6实例、计算型c5、计算型c6、通用型g5和通用型g6实例的特点和适用场景:

      实例规格 性能特点 适用场景
      突发性能型 t5 处理器:2.5 GHz主频的Intel ® Xeon ® 处理器
      搭配DDR4内存
      多种处理器和内存配比
      vCPU持续提供基准性能,可突然提速,但受到CPU积分的限制
      计算、内存和网络资源的平衡
      仅支持专有网络VPC
      Web应用服务器
      轻负载应用、微服务
      开发测试压测服务应用
      共享型s6 相比上一代共享型实例规格族(xn4、n4、mn4和e4),性价比提升
      处理器:2.5 GHz主频的Intel ® Xeon ® Platinum 8269CY(Cascade Lake),睿频3.2 GHz,计算性能稳定
      搭配DDR4内存
      支持1:1、1:2、1:4多种处理器内存配比
      I/O优化实例
      支持ESSD云盘、SSD云盘和高效云盘
      仅支持专有网络VPC
      中小型网站和Web应用程序
      开发环境、构建服务器、代码存储库、微服务、测试和暂存环境等
      轻量级数据库、缓存
      轻量级企业应用、综合应用服务
      计算型 c5 I/O优化实例
      支持ESSD云盘、SSD云盘和高效云盘
      处理器与内存配比为1:2
      超高网络PPS收发包能力
      处理器:2.5 GHz主频的Intel ® Xeon ® Platinum 8163(Skylake)或者8269CY(Cascade Lake),计算性能稳定
      实例网络性能与计算规格对应(规格越高网络性能越强)
      高网络包收发场景,例如视频弹幕、电信业务转发等
      Web前端服务器
      大型多人在线游戏(MMO)前端
      数据分析、批量计算、视频编码
      高性能科学和工程应用
      计算型 c6 依托神龙架构,将大量虚拟化功能卸载到专用硬件,降低虚拟化开销,提供稳定可预期的超高性能
      I/O优化实例
      支持ESSD云盘、SSD云盘和高效云盘
      实例存储I/O性能与计算规格对应(规格越高存储I/O性能越强)
      支持开启或关闭超线程配置
      处理器与内存配比为1:2
      超高网络PPS收发包能力
      处理器:2.5 GHz主频的Intel ® Xeon ® Platinum 8269CY(Cascade Lake),睿频3.2 GHz,计算性能稳定
      实例网络性能与计算规格对应(规格越高网络性能越强)
      支持变配为g6或r6
      高网络包收发场景,例如视频弹幕、电信业务转发等
      Web前端服务器
      大型多人在线游戏(MMO)前端
      数据分析、批量计算、视频编码
      高性能科学和工程应用
      通用型 g5 I/O优化实例
      支持ESSD云盘、SSD云盘和高效云盘
      处理器与内存配比为1:4
      超高网络PPS收发包能力
      处理器:2.5 GHz主频的Intel ® Xeon ® Platinum 8163(Skylake)或者8269CY(Cascade Lake),计算性能稳定
      实例网络性能与计算规格对应(规格越高网络性能越强)
      高网络包收发场景,例如视频弹幕、电信业务转发等

      各种类型和规模的企业级应用
      中小型数据库系统、缓存、搜索集群
      数据分析和计算
      计算集群、依赖内存的数据处理
      通用型 g6 依托神龙架构,将大量虚拟化功能卸载到专用硬件,降低虚拟化开销,提供稳定可预期的超高性能
      I/O优化实例
      支持ESSD云盘、SSD云盘和高效云盘
      实例存储I/O性能与计算规格对应(规格越高存储I/O性能越强)
      处理器与内存配比为1:4
      支持开启或关闭超线程配置
      超高网络PPS收发包能力
      处理器:2.5 GHz主频的Intel ® Xeon ® Platinum 8269CY(Cascade Lake),睿频3.2 GHz,计算性能稳定
      实例网络性能与计算规格对应(规格越高网络性能越强)
      支持变配为c6或r6
      高网络包收发场景,例如视频弹幕、电信业务转发等

      各种类型和规模的企业级应用
      网站和应用服务器
      游戏服务器
      中小型数据库系统、缓存、搜索集群
      数据分析和计算
      计算集群、依赖内存的数据处理

      二、入门级和企业级实例性能差距
      看了上面的规格配置大家应该心里有数了,入门级实例和企业级实例差距是巨大的。同样是2核4G实例,入门级实例因为有CPU基线20%的限制导致整体性能打了折扣,所以说仅适用于学习、测试、轻量级网站等对CPU要求比较低的业务环境中。

      企业级实例就好说了,独享系统全部资源,无CPU性能限制,可以满载运行。如果我们是个人用户能不能使用呢?当然是可以的,也不需要企业认证之类的,只是付的费用多一些罢了。如果你是新用户第一次购买阿里云产品,一定要利用好新用户的首购资格,阿里云针对新用户的优惠幅度是最大的。一定要通过阿里云官网最新活动栏的各种促销活动去买,可以节省很多。

      三、如何选择
      知道了配置的区别、性能差距,如何选择就好简单了。如果你就是买来学习下服务器运维知识、测试/跑一跑代码、轻量级网站等是可以在突发性能t5实例中运行的,只要注意别达到CPU性能基线就可以了,如果要性能更高一点,就选择共享型S6实例的云服务器去购买。
      如果你是企业用户,放一些企业官网、品牌、门户型的一些网站,那就选择计算型和通用型的,以免网站流量或者数据处理比较高导致云服务器跑不动的情况出现。

      特别是一些企业网站达到日PV一万开外的访问量了,一定要选择计算型和通用型实例的云服务器,性能好,就不要选择突发性t5和共享型s6的实例了。

      购买推荐:阿里云小站是集阿里云服务器代金券、云产品通用代金券、云服务器限时秒杀、新用户1折、爆款云产品5折(老用户可买)以及阿里云热门活动入口为一体的综合优惠平台。
      代金券.png

      ]]>
      定义下一代存储,打造全新一代数据基础设施 Fri, 20 Jun 2025 02:20:33 +0800 2020年7月14日,阿里云存储新品发布会——第五期完美落幕,此次发布会阿里云为大家带来了:快照极速可用、HBR快照管理、数据加工与投递等7款新功能的发布,帮助大家一起来了解更丰富的阿里云存储产品家族及行业应用场景最佳实践。

      1、快照服务重磅升级,数据保护RTO进入秒级时代
      阿里云发布“快照极速可用”功能,其是基于盘古2.0机制的快照服务,备份速度快如闪电,能够在数秒内完成快照创建、云盘回滚操作,将业务等待时间缩短至秒级别。目前“快照极速可用”功能已经全地域上线,支持所有ESSD系列云盘规格。

      2、HBR快照管理 – 可视化界面全面提升用户体验

       HBR在阿里云上首次推出可视化快照管理功能,ECS视角的全新跑道式快照点直观呈现,使用情况一目了然,极简操作步骤,快速搭建测试开发和数据分析环境,全面提升客户体验。目前该功能免费开放,欢迎大家来体验不一样的数据保护方式。
      

      3、文件存储CPFS升级,专为高性能场景打造
      阿里云文件存储CPFS性能升级,拥有百GB/s的吞吐和百万IOPS,专为HPC高性能计算和AI深度学习打造,同时拥有丰富的企业特性、云上云下混合云架构,帮助高性能计算业务快速上云。

      4、混合云存储阵列软硬件全面升级,性能翻番
      混合云存储阵列软硬件全面升级,在价格不变的基础上,实现了性能翻倍。它将云存储的高性价比和高可扩展性与本地数据中心架构相结合,以更优的性价比帮助客户轻松实现数据在本地数据中心和公共云之间的无缝流动。

      5、表格存储索引全面升级,为用户提供极致计算分析体验
      表格存储发布支持计算引擎如Spark访问索引加速,相比于传统的NoSQL数据存储,提供的索引加速能力提升10-100倍计算速度。让用户能够更实时把握业务情况,挖掘数据价值。

      6、日志服务数据加工与投递服务发布,提供开箱即用的数据ETL和流转能力
      日志服务SLS数据加工和数据投递商业化发布,内置多种加工函数和算子,秒级延迟,一键配置多种数据投递目标,任务可监控可告警,提供开箱即用的数据ETL和流转能力,帮助客户从传统ETL方案数天或数月的搭建和调试中释放,更好地专注业务。

      7、内容分发解决方案,助力企业业务出海
      阿里云对象存储OSS联手阿里云CDN在海外region推出内容分发+源站存储低成本解决方案,这是阿里云OSS在继与海外知名CDN厂商Cloudflare合作以来,更进一步地扩大了对OSS海外用户的福利策略,同时结合OSS传输加速功能,全面提升跨境访问能力,助力企业扬帆出海 。

      阿里云存储,经过十年的技术升级与产品打磨,致力于帮助企业打破数据存储和流通的边界,满足不同企业的多样化需求,为企业数字化转型提供数据核心价值。

      ]]>
      再出王牌:阿里云 Jindo DistCp 全面开放使用,成为阿里云数据迁移利器 Fri, 20 Jun 2025 02:20:33 +0800 作者:王涛,花名扬礼,阿里巴巴计算平台事业部 EMR 开发工程师. 目前从事开源大数据存储计算方面的开发和优化工作。


      随着阿里云JindoFS SDK的全面放开使用,基于JindoFS SDK的阿里云数据迁移利器Jindo DistCp现在也全面面向用户开放使用。Jindo DistCp是阿里云E-MapReduce团队开发的大规模集群内部和集群之间分布式文件拷贝的工具。其使用MapReduce实现文件分发,错误处理和恢复,把文件和目录的列表作为map/reduce任务的输入,每个任务会完成源列表中部分文件的拷贝。目前全量支持hdfs->oss,hdfs->hdfs,oss->hdfs,oss->oss的数据拷贝场景,提供多种个性化拷贝参数和多种拷贝策略。重点优化hdfs到oss的数据拷贝,通过定制化CopyCommitter,实现No-Rename拷贝,并保证数据拷贝落地的一致性。功能全量对齐S3 DistCp和HDFS DistCp,性能较HDFS DistCp有较大提升,目标提供高效、稳定、安全的数据拷贝工具。本文主要介绍如何使用Jindo DistCp来进行基本文件拷贝,以及如何在不同场景下提高数据拷贝性能。值得一提的是,此前 Jindo DistCp 仅限于E-MapReduce产品内部使用,此次全方位面向整个阿里云OSS/HDFS用户放开,并提供官方维护和支持技术,欢迎广大用户集成和使用。

      大数据和数据迁移工具

      在传统大数据领域,我们经常使用HDFS作为底层存储,并且在HDFS存储大规模的数据。在进行数据迁移、数据拷贝的场景中,大家选择最常用的是Hadoop自带的DistCp工具,但是其不能很好利用对象存储系统如OSS的特性,导致效率低下并且不能最终保证一致性,提供的功能选项也比较简单,不能很好的满足用户的需求。此时一个高效、功能丰富的数据迁移工具成为影响软件搬栈、业务上云的重要影响因素。

      Hadoop DistCp

      Hadoop DistCp是Hadoop集成的分布式数据迁移工具,提供了基本文件拷贝、覆盖拷贝、指定map并行度、log输出路径等功能。在Hadoop2x上对DistCp进行了部分优化例如拷贝策略的选择,默认使用 uniformsize(每个 map 会平衡文件大小)如果指定 dynamic,则会使用 DynamicInputFormat。这些功能优化了普通hdfs间数据拷贝,但是对于对象存储系统如OSS缺少数据写入方面的优化。

      S3 DistCp

      S3 DistCp是AWS为S3提供的distcp工具, S3DistCp是Hadoop DistCp 的扩展,它进行了优化使得其可以和S3结合使用,并新增了一些实用功能。新增功能如增量复制文件、复制文件时指定压缩方式、根据模式进行数据聚合、按照文件清单进行拷贝等。S3 DistCp依靠S3对象存储系统,目前只能在AWS EMR内部使用,并不开放给普通用户。

      Jindo DistCp

      Jindo DistCp是一个简单易用的分布式文件拷贝工具,目前主要用在E-Mapreduce集群内,主要提供hdfs到OSS的数据迁移服务,相比于Hadoop DistCp和S3 DistCp,Jindo DistCp做了很多优化以及新增了许多个性化功能,并且深度结合OSS对象存储的特性,定制化CopyCommitter,实现No-Rename拷贝,大大缩短上云数据迁移时间消耗。现在Jindo DistCp对外开放使用,我们可以使用该功能来进行上云数据迁移,获得OSS数据迁移利器。

      为什么使用 Jindo DistCp?

      1、效率高,在测试场景中最高可到1.59倍的加速。
      2、基本功能丰富,提供多种拷贝方式和场景优化策略。
      3、深度结合OSS,对文件提供直接归档和低频、压缩等操作。
      4、实现No-Rename拷贝,保证数据一致性。
      5、场景全面,可完全替代Hadoop DistCp,支持多Hadoop版本(如有问题可提issue)

      Jindo DistCp 兼容性如何?

      Jindo DistCp目前支持Hadoop2.7+和最新的Hadoop3.x,以两个不同的jar形式提供服务,依赖Hadoop环境并且不会和Hadoop DistCp产生冲突。在阿里云EMR内部可直接提供Jindo DistCp的服务,用户无需进行jar包下载。用户下载jar包后,再通过参数或者Hadoop配置文件配上oss的AK即可使用。

      使用 Jindo DistCp 性能提升多少?

      我们做了一个Jindo DistCp和Hadoop DistCp的性能对比,在这个测试中我们以hdfs到oss为主要场景,利用Hadoop自带的测试数据集TestDFSIO分别生成1000个10M、1000个500M、1000个1G大小的文件进行从hdfs拷贝数据到oss上的测试过程。

      截屏2020-07-14 下午6.41.02.png
      45.png

      分析测试结果,可以看出Jindo DistCp相比Hadoop DistCp具有较大的性能提升,在测试场景中最高可达到1.59倍加速效果。

      使用工具包

      1. 下载jar包

      我们去github repo下载最新的jar包 jindo-distcp-x.x.x.jar
      注意:目前Jar包只支持Linux、MacOS操作系统,因为SDK底层采用了native代码。

      2. 配置OSS访问AK

      您可以在命令中使用程序执行时指定--key、--secret、--endPoint参数选项来指定AK。

      示例命令如下:

      hadoop jar jindo-distcp-2.7.3.jar --src /data/incoming/hourly_table --dest oss://yang-hhht/hourly_table --key yourkey --secret yoursecret --endPoint oss-cn-hangzhou.aliyuncs.com

      您也可以将oss的ak、secret、endpoint预先配置在 hadoop的 core-site.xml 文件里 ,避免每次使用时临时填写ak。

      <configuration>
          <property>
              <name>fs.jfs.cache.oss-accessKeyId</name>
              <value>xxx</value>
          </property>
          <property>
              <name>fs.jfs.cache.oss-accessKeySecret</name>
              <value>xxx</value>
          </property>
          <property>
              <name>fs.jfs.cache.oss-endpoint</name>
              <value>oss-cn-xxx.aliyuncs.com</value>
          </property>
      </configuration>

      另外,我们推荐配置免密功能,避免明文保存accessKey,提高安全性。

      使用手册

      Jindo DistCp提供多种实用功能及其对应的参数选择,下面介绍参数含义及其示例

      截屏2020-07-14 下午6.53.08.png
      截屏2020-07-14 下午6.53.32.png

      截屏2020-07-14 下午6.53.50.png

      更多详细使用细节,请参考Jindo DistCp使用指南

      联系我们

      Jindo DistCp还在日益完善,后续会不断根据用户需求进行优化。欢迎大家下载使用Jindo DistCp,如果遇到任何问题,请随时联系阿里云E-Mapreduce团队,或者在github上提交issue,我们将尽快为您解答。


      相关阅读

      重磅:阿里云 JindoFS SDK 全面开放使用,OSS 文件各项操作性能得到大幅提升

      JindoFS - 分层存储

      EMR Spark-SQL性能极致优化揭秘 Native Codegen Framework

      Jindo SQL 性能优化之 RuntimeFilter Plus

      JindoFS: 云上大数据的高性能数据湖存储方案

      JindoFS概述:云原生的大数据计算存储分离方案

      JindoFS解析 - 云上大数据高性能数据湖存储方案


      后续我们也会在云栖社区和钉钉群分享更多的 Jindo 技术干货,欢迎有兴趣的同学加入 【阿里云EMR钉钉群】进行交流和技术分享。
      产品群.JPG

      ]]>
      从0到1搭建车企数字化营销中台(2):什么是数字化营销 Fri, 20 Jun 2025 02:20:33 +0800 数字化的核心要素为:
      1)获取:针对某一个自然或社会现象来获取有关于它的信息
      2)表达:用某一种表达方式来表达所获得的信息
      3)存储:把表达好的信息存储在某一种物理媒体上
      4)传输:用某一种传送机制来传输信息
      5)处理:将信息通过计算来进行针对性的处理
      6)交付:将处理好的信息交付给端点来达到目的

      根据杰罗姆·麦卡锡在《基础营销学》下的定义:营销是指某一组织为满足顾客而从事的一系列活动。

      顾名思义,数字化营销是使用数字化通路来满足客户需求的一系列活动。

      整个营销活动可以分为以下几个步骤:
      1)洞察客户需求
      2)识别客户
      3)客户建立连接并触达客户
      4)与客户沟通与互动
      5)满足客户需求促成转化
      6)激励并放大客户价值

      将营销活动的6个动作进行数字化,总结出4大数字化营销模型:
      1)数字化识别与洞察:客户ID归一识别,个人行为数字化后全方位洞察;
      2)数字化触达与连接:数字化让用户行为发生着巨大变化,也驱动着企业与用户的连接方式发生转变,新兴触点层出不穷,全渠道连接和触达必不可少;
      3)数字化沟通与互动:数字化加速,各种沟通和互动方式层出不穷,文字、语音、视频等内容全渠道击穿,传递关系价值而非交易价值:
      4)数字化转化与激励:不止于交易,更关注经营用户网络,建立可持续关系,放大用户价值

      结合这4大数字化营销模型,车企的数字化营销平台该怎么设计呢?下篇文章将总点介绍车企的数字化营销平台的产品架构。

      作者:赵松,微信公众号:松果子聊数字化,数跑科技营销增长平台产品线负责人、阿里云大数据MVP,前阿里影业数据产品专家,7年数字化从业经历。

      ]]>
      从0到1搭建车企数字化营销中台(3):产品定义 Fri, 20 Jun 2025 02:20:33 +0800 1、产品概述
      基于轻量级双中台(业务中台和数据中台)构建的客户运营平台、粉丝互动平台、营销推广平台和数据洞察平台共同组成了整个车企的数字化营销中台,为客户提供全渠道、全旅程的个性化营销体验。

      2、产品架构
      数字化营销中台由运营前台和轻量级双中台组成,运营前台主要包含:客户运营、粉丝互动、营销推广、数据洞察四个部分;轻量级双中台主要包含轻量级业务中台和轻量级数据中台,轻量级业务中台由泛会员、活动中心、内容中心、营销自动化组成,轻量级数据中台主要指客户数据平台(CDP),如下图所示。

      产品架构22.png

      1、运营前台:
      1) 客户运营:客户运营主要提供人群运营、场景营销、客户洞察和客户管理等功能,实现客户的全渠道全生命周期的持续运营;
      2) 粉丝互动:粉丝互动主要提供粉丝管理、互动管理、群发管理、授权管理等功能,通过接入新的互动媒体渠道,实现新媒体矩阵营销,实现存量粉丝激活,统一互动;
      3) 营销推广:营销推广主要提供广告授权管理、广告数据接入、投放渠道接入等功能,通过将公域广告投放数据和私域数据打通,实现客户转化数据的全链路跟踪;
      4) 数据洞察:数据大盘主要提供转化漏斗、用户分析、专题分析、ROI分析等功能,实现用户运营指标和销售全链路指标可视化展现及洞察分析;

      2、轻量级双中台:
      轻量级业务中台:

      1)泛会员:建立一套完整的会员忠诚度体系,结合积分和卡券权益激励,持续提升用户忠诚度。
      2)活动中心:通过丰富的活动工具快速构建活动,结合细分的活动人群,从合适的渠道触达客户;激活客户,增强粘性。
      3)内容中心:统一管理营销内容,保持营销内容的一致性和标志性;同时提供标准组件模板快速构建营销内容。
      4)营销自动化:对营销渠道和广告位进行统一管理,让投放精准有序、可控、可溯源;同时提供高效的营销自动化规则引擎,把营销人员从重复体力劳动中释放出来,聚焦营销场景设计。

      轻量级数据中台:

       轻量级数据中台主要指只覆盖客户域的数据中台,这里指客户数据平台(CDP);
      客户数据平台:全旅程采集客户行为和业务数据,对数据进行清洗、归集、建模后形成丰富的标签画像;洞察客户,形成细分人群,驱动千人千面的个性化精准营销。
      

      作者:赵松,微信公众号:松果子聊数字化,数跑科技营销增长平台产品线负责人、阿里云大数据MVP,前阿里影业数据产品专家,7年数字化从业经历。

      ]]>
      从0到1搭建车企数字化营销中台(4):客户数据平台(CDP) Fri, 20 Jun 2025 02:20:33 +0800 客户数据平台(CDP)在数字化营销过程中起着核心驱动作用,通过打通客户全渠道、全旅程触点数据,形成基于客户全旅程的数据链路,通过数据建模实现对客户的全方位洞察,并形成用户分层运营的策略,指导运营计划的精准实施,在什么时间,什么渠道,针对什么人群,推送什么权益钩子,实现精准转化,最大化营销ROI。

      业务架构.png

      1、CDP的定义
      广义的CDP是一个营销系统,它统一企业来自营销或其他渠道的客户数据,以实现客户建模、优化客户体验的目标。狭义的CDP是整合来自多个不同来源的数据,为客户洞察和交互提供数据支持;

      2、CDP的架构
      CDP基于轻量级的数据中台,主要包含源数据层、计算层、OneData(统一建模)、OneID(客户标签)、OneService(统一数据服务)、应用层。

      CDP.png

      源数据层:主要包含客户一方、二方和三方数据,一方数据主要指客户自有业务数据,如APP数据、线索数据、DMS(经销商管理系统)数据、销售助手数据等;二方数据主要指公域广告投放回流数据和DMP相关数据;三方数据主要是指主机厂外部合作的数据;
      计算层:主要基于阿里云的大数据平台Dataworks,包含数据集成、数据开发、数据运维、数据质量、数据安全等;底层计算引擎为Maxcompute和实时计算Flink;
      OneData(统一建模):主要基于维度建模理论进行数仓健身,进行ODS(源数据层)、DWD(明细数据层)、DWS(主题数据层)、ADS(应用数据层)数据分层,主要包含会员、行为、消费、活动、客户、线索六大主题域数据模型;
      OneID(客户标签):通过ID-mapping生成客户归一ID,基于oneid进行客户标签萃取,形成10大类标签:人口属性、设备属性、位置属性、客户属性、会员属性、行为属性、活动属性、消费属性、内容属性和社交属性;
      OneService(统一数据服务):统一提供离线和实时数据服务,统一口径,统一出口,统一鉴权;
      应用层:主要包含客户洞察、客户360视图、人群细分和标签管理
      1)客户洞察:主要为全旅程分析、RFM分析、人群分析等
      2)客户360视图:根据客户10大类属性100+标签形成客户360度画像;
      3)人群细分:主要为人群圈选、人群组合、人群管理;
      4)标签管理:主要为标签上下架、系统标签、分层标签;

      3、CDP的价值
      CDP围绕数字化营销的数字化,长期价值为多方化、资产化、智能化、安全化;
      多方化:集成一方数据,打通二方数据,利用三方数据,通过一、二、三方数据扩展客户维度,形成对客户更加精准的洞察;
      资产化:通过自动化的标签加工方式不断的将客户数据转化数据资产;
      智能化:围绕数字化营销,利用机器学习算法不断沉淀相关营销模型以达到增长的目标,如AIPL模型、RFM模型、购车意愿模型、车型偏好模型等;
      安全化:打造可信安全的客户数据平台是数字化营销中台的基石,通过数据加密、隐私计算、多方计算实现数据安全和隐私保护。

      CDP的价值.png

      作者:赵松,微信公众号:松果子聊数字化,数跑科技营销增长平台产品线负责人、阿里云大数据MVP,前阿里影业数据产品专家,7年数字化从业经历。

      ]]>
      阿里云ECS云服务器规格适用场景对照表 Fri, 20 Jun 2025 02:20:33 +0800 阿里云ECS云服务器分为多种实例规格,如通用型、计算型、高主频型、共享型、异构GPU型及裸金属服务器等规格,ECS云服务器规格的选择根据实际使用场景选择,服务器教程网分享ECS实例规格适用场景说明表:

      ECS云服务器实例规格使用场景对照

      阿里云ECS云服务器实例规格可以分为以下多种规格:

      • x86计算:通用型、计算型、内存型、大数据型、本地SSD、高主频及共享型等;
      • 异构计算GPU/FPGA/NPU:GPU计算型、GPU虚拟化型、FPGA计算型、NPU计算型;
      • 弹性裸金属服务器(神龙):CPU型、GPU型;
      • 超级计算集群:CPU型、GPU型。

      大家可以参考下方实际应用场景选择对应的ECS实例规格:

      通用型 通用型 通用型 通用型 通用型 通用型 异构计算 异构计算 裸金属/高性能计算
      通用型g6/g5、通用网络增强sn2ne 计算型c6/c5、计算网络增强型sn1ne 内存型r6/r5、内存增强型re6、内存增强型se1ne、内存型se1 高主频hfc6/hfg6/hfr6/hfc5/hfg5 本地SSD l2/l2g/l2ne/l2gne/l1 大数据型 d2s/d1ne/d1 GPU计算型gn6i/gn6v/gn6e/gn5、轻量级GPU计算型vgn6i FPGA计算型f3/f1 弹性裸金属ebm、超级计算集群scc、弹性高性能计算e-hpc
      中小型数据库 数据处理任务 企业后台应用 高性能网 前端节点 Web服务器 批量计算 分布式分析 对战类游戏 高性能科学 工程类应用 平台广告 视频编解码 高性能数据库 数据挖掘和分析 Redis、Memcached等缓存 内存型数据库 高性能科学计算 高性能前端节点 关系型数据库 NoSQL数据库 数据仓库 内存型数据库 Hadoop/Spark集群(实时) MapReduce分布式计算 Hadoop/Spark 分布式文件系统 日志和数据处理 AI深度学习 视频处理 图形可视化 科学计算 基因组学研究 视频编解码 图像转码 金融分析 高性能数据库 高性能网站前端节点 数据处理任务 企业后台应用 高性能计算 科学计算

      大家根据实际应用场景选择对应的ECS实例规格,关于ECS云服务器实例规格CPU、内存、网络性能等参数,可以参考官方文档:ECS云服务器实例详解 - 阿里云

      ]]>
      火车头采集器如何设置http代理数据采集 Fri, 20 Jun 2025 02:20:33 +0800 一、百度搜索“快鸟代理ip”,登录已注册的账号;点击“获取代理IP”,在“获取代理IP”界面设置好相关参数;点击“生成API链接”,然后点击“复制链接”按钮;

      二、打开“火车头采集器”软件,登录后点击“http二级代理”按钮;

      Snipaste_2020-07-10_19-10-36.png

      三、弹出“http二级代理”窗口,选择“商业代理”选项卡;

      Snipaste_2020-07-10_19-11-22.png

      四、在“请求网址”文本框上粘贴刚刚复制的提取代理IP链接,点击“测试获取代理ip”按钮;

      Snipaste_2020-07-10_19-12-42.png

      五、点击“启动代理”按钮;

      Snipaste_2020-07-10_19-13-00.png

      ]]>
      DataWorks百问百答41:DataWorks上如何保护敏感数据? Fri, 20 Jun 2025 02:20:33 +0800 在企业的运营过程中,会产生大量的数据,其中有一部分是企业的核心数据资产,比如客户数据、企业运营数据、成本数据等等,这部分高价值的数据需要得到合理的保护。DataWorks作为一款企业级的大数据开发平台,在数据安全方面与蚂蚁金服的数据安全团队合作,为您做到金融级别的安全防护。下面演示一下如何使用DataWorks的数据保护伞模块,通过数据脱敏,完成敏感数据的保护。

      定义数据安全等级

      在保护敏感数据前,我们需要站在管理视角,先定义好敏感等级,便于对数据进行分级管控。在数据保护伞>规则配置>分级信息管理中,可以定义全局的分级管理策略,等级越高的数据,安全等级越高,目前可定义的最高安全等级是9级。
      image.png

      定义敏感数据

      在定义完数据安全等级后,需要定义敏感数据,并与数据安全等级绑定。
      image.png

      在数据保护伞 > 规则配置 > 数据识别规则中,点击新建规则。在新建识别规则的过程中,会经历三个步骤:配置基本信息、配置规则、配置预览。我这里数据类型选择的是自定义添加,类型名称为用户画像;数据名称选择自定义添加,名称为用户手机号;规则责任人是我当前账号名称。
      image.png

      点击下一步,进入配置规则;选择数据安全等级,并进行绑定;绑定后到了本规则的核心配置,设置数据识别规则。此处选择的是内容扫描,通过正则匹配的方式,将匹配上的数据,打标为敏感数据。

      image.png

      注意:若选择字段扫描,输入格式为project.table.column,其中任意一段都可以同*作为通配符。

      数据识别规则配置完成后,点击下一步,进入配置预览界面,预览无误后,可点击保存或保存并生效。
      image.png
      配置完成后,可在数据识别规则列表中查看。
      image.png

      设置脱敏规则

      当敏感数据定义完成后,可设置数据脱敏规则,来保护敏感数据。在数据保护伞 > 规则配置 > 数据脱敏管理中,点击新建规则,需选择脱敏规则、负责人、脱敏方式等信息,设置完成后,点击确认。
      image.png

      目前脱敏方式支持2种,hash和掩盖。

      完成脱敏规则新建后,需打开规则状态。
      image.png

      验证脱敏

      在验证脱敏前,需要在DataStudio模块数据开发设置中的工作空间配置里,启用页面查询内容脱敏功能。
      image.png

      启用后,进入数据开发界面,找到ODPS SQL节点,写下查询手机号的语句,进行验证。
      image.png

       注意:若查询结果未脱敏,可按如下步骤检查:

      1. 请检查是否未启用页面查询脱敏功能。
      2. 请检查数据识别规则是否启用
      3. 请检查数据脱敏规则是否启用
      4. 请检查,您是否在脱敏规则的白名单中

      DataWorks百问百答历史记录 请点击这里查看>>

      更多DataWorks技术和产品信息,欢迎加入【DataWorks钉钉交流群】

      ]]>
      为什么阿里、头条、美团这些互联网大厂都在用Spring Boot? Fri, 20 Jun 2025 02:20:33 +0800 前言

      现在各大技术社区 Spring Boot 的文章越来越多,Spring Boot 相关的图文、视频教程越来越多,使用 Spring Boot 的互联网公司也越来越多; Java 程序员现在出去面试, Spring Boot 已经成了必问的内容。

      一切都在证明,Spring Boot 已经成为了 Java 程序员必备的技能。并且可以预见的是未来 Spring Boot 的发展还会更好。

      所以对Java程序员来说其中不乏说对 Spring Boot 非常熟悉的,然后当问到一些 Spring Boot 核心功能和原理的时候,没人能说得上来,或者说不到点上,可以说一个问题就问趴下了!(问题:你能讲下为什么我们要用 Spring Boot 吗?)

      相信我,上面这些类似的问题,90%有经验的Java程序员超都曾遇见过!但很少有系统化的回答。

      因此,总结了这份Spring Boot核心知识点实战教程,通过这份教程,带你梳理Spring Boot 技术体系。

      文末有彩蛋~

      Spring Boot2教程

      在Spring Boot项目中,正常来说是不存在XML配置,这是因为Spring Boot不推荐使用 XML ,注意,并非不支持,Spring Boot 推荐开发者使用 Java 配置来搭建框架,Spring Boot 中,大量的自动化配置都是通过 Java 配置来实现的,这一套实现方案,我们也可以自己做,即自己也可以使用纯 Java 来搭建一个 SSM 环境,即在项目中,不存在任何 XML 配置,包括 web.xml 。

      环境要求:

      使用纯 Java 来搭建 SSM 环境,要求 Tomcat 的版本必须在 7 以上。

      1、创建工程

      创建一个普通的 Maven工程(注意,这里可以不必创建Web工程),并添加SpringMVC的依赖,同时,这里环境的搭建需要用到 Servlet ,所以我们还需要引入 Servlet 的依赖(一定不能使用低版本的Servlet),最终的 pom.xml 文件如下:

      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.6.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
      </dependency>

      2 、添加 Spring 配置

      工程创建成功之后,首先添加 Spring 的配置文件,如下:

      @Configuration
      @ComponentScan(basePackages = "org.javaboy", useDefaultFilters = true,
      excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
      Controller.class)})
      public class SpringConfig {
      }

      关于这个配置,我说如下几点:

      @Configuration 注解表示这是一个配置类,在我们这里,这个配置的作用类似于applicationContext.xml

      @ComponentScan 注解表示配置包扫描,里边的属性和 xml 配置中的属性都是一一对应的,useDefaultFilters 表示使用默认的过滤器,然后又除去 Controller 注解,即在 Spring 容器中扫描除了 Controller 之外的其他所有 Bean 。

      3、 添加 SpringMVC 配置

      接下来再来创建 springmvc 的配置文件:

      @Configuration
      @ComponentScan(basePackages = "org.javaboy",useDefaultFilters =
      false,includeFilters = {@ComponentScan.Filter(type =
      FilterType.ANNOTATION,classes = Controller.class)})
      public class SpringMVCConfig {
      }

      注意,如果不需要在SpringMVC中添加其他的额外配置,这样就可以了。即视图解析器、JSON解析、文件上传......等等,如果都不需要配置的话,这样就可以了。

      4、配置 web.xml

      此时,我们并没 web.xml 文件,这时,我们可以使用Java代码去代替 web.xml 文件,这里会用到WebApplicationInitializer ,具体定义如下:

      public class WebInit implements WebApplicationInitializer {
        public void onStartup(ServletContext servletContext) throws ServletException
      {
          //首先来加载 SpringMVC 的配置文件
          AnnotationConfigWebApplicationContext ctx = new
      AnnotationConfigWebApplicationContext();
          ctx.register(SpringMVCConfig.class);
          // 添加 DispatcherServlet
          ServletRegistration.Dynamic springmvc =
      servletContext.addServlet("springmvc", new DispatcherServlet(ctx));
          // 给 DispatcherServlet 添加路径映射
          springmvc.addMapping("/");
          // 给 DispatcherServlet 添加启动时机
          springmvc.setLoadOnStartup(1);
       }
      }

      WebInit 的作用类似于 web.xml,这个类需要实现 WebApplicationInitializer 接口,并实现接口中的方法,当项目启动时,onStartup 方法会被自动执行,我们可以在这个方法中做一些项目初始化操作,例如加载 SpringMVC 容器,添加过滤器,添加 Listener、添加 Servlet 等。关注公种浩:程序员追风,回复 005 获取这份整理好的Spring Boot实战教程。

      注意:

      由于我们在WebInit中只是添加了SpringMVC的配置,这样项目在启动时只会去加载SpringMVC容器,而不会去加载 Spring 容器,如果一定要加载 Spring 容器,需要我们修改 SpringMVC 的配置,在SpringMVC 配置的包扫描中也去扫描 @Configuration 注解,进而加载 Spring 容器,还有一种方案可以解决这个问题,就是直接在项目中舍弃 Spring 配置,直接将所有配置放到 SpringMVC 的配置中来完成,这个在 SSM 整合时是没有问题的,在实际开发中,较多采用第二种方案,第二种方案,SpringMVC 的配置如下:

      @Configuration
      @ComponentScan(basePackages = "org.javaboy")
      public class SpringMVCConfig {
      }

      这种方案中,所有的注解都在 SpringMVC 中扫描,采用这种方案的话,则 Spring 的配置文件就可以删除了。

      5、测试

      最后,添加一个 HelloController ,然后启动项目进行测试:

      @RestController
      public class HelloController {
        @GetMapping("/hello")
        public String hello() {
          return "hello";
       }
      }

      启动项目,访问接口,结果如下:

      Spring Boot全局异常处理

      在Spring Boot项目中 ,异常统一处理,可以使用Spring中@ControllerAdvice来统一处理,也可以自己来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,我们分别来看。

      默认情况下,Spring Boot 中的异常页面 是这样的:

      我们从这个异常提示中,也能看出来,之所以用户看到这个页面,是因为开发者没有明确提供一个/error 路径,如果开发者提供了 /error 路径 ,这个页面就不会展示出来,不过在 Spring Boot 中,提供/error 路径实际上是下下策,Spring Boot本身在处理异常时,也是当所有条件都不满足时,才会去找 /error 路径。那么我们就先来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种,一种是静态页面,另一种是动态页面。

      静态异常页面

      自定义静态异常页面,又分为两种,第一种 是使用HTTP响应码来命名页面,例如404.html、405.html、500.html ....,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。

      默认是在 classpath:/static/error/ 路径下定义相关页面:

      此时,启动项目,如果项目抛出 500 请求错误,就会自动展示 500.html 这个页面,发生 404 就会展示404.html 页面。如果异常展示页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展示 500.html 页面。

      动态异常页面

      动态的异常页面定义方式和静态的基本 一致,可以采用的页面模板有 jsp、freemarker、thymeleaf。

      动态异常页面,也支持 404.html 或者 4xx.html ,但是一般来说,由于动态异常页面可以直接展示异常详细信息,所以就没有必要挨个枚举错误了 ,直接定义 4xx.html(这里使用thymeleaf模板)或者5xx.html 即可。

      注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面。

      页面定义如下:

      页面内容如下:

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
        <meta charset="UTF-8">
        <title>Title</title>
      </head>
      <body>
      <h1>5xx</h1>
      <table border="1">
        <tr>
          <td>path</td>
          <td th:text="${path}"></td>
        </tr>
        <tr>
          <td>error</td>
          <td th:text="${error}"></td>
        </tr>
        <tr>
          <td>message</td>
          <td th:text="${message}"></td>
        </tr>
        <tr>
          <td>timestamp</td>
          <td th:text="${timestamp}"></td>
        </tr>
        <tr>
          <td>status</td>
          <td th:text="${status}"></td>
        </tr>
      </table>
      </body>
      </html>

      默认情况下,完整的异常信息就是这5条,展示 效果如下 :

      如果动态页面和静态页面同时定义了异常处理页面,例如 classpath:/static/error/404.html 和classpath:/templates/error/404.html 同时存在时,默认使用动态页面。即完整的错误页面查找

      方式应该是这样:

      发生了 500 错误-->查找动态 500.html 页面-->查找静态 500.html --> 查找动态 5xx.html-->查找静态5xx.html。

      自定义异常数据

      默认情况下,在 Spring Boot 中,所有的异常数据其实就是上文所展示出来的 5 条数据,这 5 条数据定义在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 类中,具体定义在 getErrorAttributes 方法中 :

      public Map<String, Object> getErrorAttributes(ServerRequest request,
              boolean includeStackTrace) {
          Map<String, Object> errorAttributes = new LinkedHashMap<>();
          errorAttributes.put("timestamp", new Date());
          errorAttributes.put("path", request.path());
          Throwable error = getError(request);
          HttpStatus errorStatus = determineHttpStatus(error);
          errorAttributes.put("status", errorStatus.value());
          errorAttributes.put("error", errorStatus.getReasonPhrase());
          errorAttributes.put("message", determineMessage(error));
          handleException(errorAttributes, determineException(error),
      includeStackTrace);
          return errorAttributes;
      }

      DefaultErrorAttributes 类本身则是在

      org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,如果开发者没有自己提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个 ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。

      基于此 ,开发者自定义 ErrorAttributes 有两种方式 :

      1. 直接实现 ErrorAttributes 接口

      2. 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。

      具体定义如下:

      @Component
      public class MyErrorAttributes  extends DefaultErrorAttributes {
        @Override
        public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean
      includeStackTrace) {
          Map<String, Object> map = super.getErrorAttributes(webRequest,
      includeStackTrace);
          if ((Integer)map.get("status") == 500) {
            map.put("message", "服务器内部错误!");
         }
          return map;
       }
      }

      定义好的 ErrorAttributes 一定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的DefaultErrorAttributes 了,运行效果如下图:

      自定义异常视图

      异常视图默认就是前面所说的静态或者动态页面,这个也是可以自定义的,首先 ,默认的异常视图加载逻辑在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 类的errorHtml 方法中,这个方法用来返回异常页面+数据,还有另外一个 error 方法,这个方法用来返回异常数据(如果是 ajax 请求,则该方法会被触发)。

      @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
      public ModelAndView errorHtml(HttpServletRequest request,
              HttpServletResponse response) {
          HttpStatus status = getStatus(request);
          Map<String, Object> model =
      Collections.unmodifiableMap(getErrorAttributes(
                  request, isIncludeStackTrace(request,
      MediaType.TEXT_HTML)));
          response.setStatus(status.value());
          ModelAndView modelAndView = resolveErrorView(request, response, status,
      model);
          return (modelAndView != null) ? modelAndView : new ModelAndView("error",
      model);
      }

      在该方法中 ,首先会通过 getErrorAttributes 方法去获取异常数据(实际上会调用到 ErrorAttributes的实例 的 getErrorAttributes 方法),然后调用 resolveErrorView 去创建一个 ModelAndView ,如果这里创建失败,那么用户将会看到默认的错误提示页面。

      正常情况下, resolveErrorView 方法会来到 DefaultErrorViewResolver 类的 resolveErrorView 方法中:

      @Override
      public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
      status,
              Map<String, Object> model) {
          ModelAndView modelAndView = resolve(String.valueOf(status.value()),
      model);
          if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
              modelAndView = resolve(SERIES_VIEWS.get(status.series()),
      model);
         }
          return modelAndView;
      }

      在这里,首先以异常响应码作为视图名分别去查找动态页面和静态页面,如果没有查找到,则再以 4xx或者 5xx 作为视图名再去分别查找动态或者静态页面。

      要自定义异常视图解析,也很容易 ,由于 DefaultErrorViewResolver 是在ErrorMvcAutoConfiguration 类中提供的实例,即开发者没有提供相关实例时,会使用默认的DefaultErrorViewResolver ,开发者提供了自己的 ErrorViewResolver 实例后,默认的配置就会失效,因此,自定义异常视图,只需要提供 一个 ErrorViewResolver 的实例即可:

      @Component
      public class MyErrorViewResolver extends DefaultErrorViewResolver {
        public MyErrorViewResolver(ApplicationContext applicationContext,
      ResourceProperties resourceProperties) {
          super(applicationContext, resourceProperties);
       }
        @Override
        public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
      status, Map<String, Object> model) {
          return new ModelAndView("/aaa/123", model);
       }
      }

      实际上,开发者也可以在这里定义异常数据(直接在 resolveErrorView 方法重新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不可以直接修改),而不需要自定义 MyErrorAttributes。定义完成后,提供一个名为 123 的视图,如下图:

      如此之后,错误试图就算定义成功了。

      总结

      实际上也可以自定义异常控制器 BasicErrorController ,不过松哥觉得这样太大动干戈了,没必要,前面几种方式已经可以满足我们的大部分开发需求了。如果是前后端分离架构,异常处理还有其他一些处理方案,这个以后和大家聊。

      篇幅有限,其他内容就不在这里一一展示了,这份Spring Boot实战教程已整理成一份PDF文档,共有200多页。关注公种浩:程序员追风,回复 005 获取这份整理好的Spring Boot实战教程。

      i

      最后

      欢迎大家一起交流,喜欢文章记得点ge 赞哟,感谢支持!

      ]]>
      MaxCompute问答整理之2020-06月 Fri, 20 Jun 2025 02:20:33 +0800 问题一、MaxCompute的表有无索引?
      没有索引,不过Hash Clustering可以提供类似数据库里cluster index的效果。
      具体可参考:https://help.aliyun.com/document_detail/73768.html

      问题二、DataWorks调度依赖关系中,两个不同业务流程的节点如何依赖?
      比如我们有两个业务流程A和B,可以让B业务流程首个节点依赖A业务流程的最后一个节点。
      具体可参考:https://developer.aliyun.com/article/759463

      问题三、MaxCompute时间类型字段能不能不带时分秒?
      可以使用date数据类型。开启MaxCompute2.0可以使用date类型。
      具体可参考:
      https://help.aliyun.com/document_detail/159541.html

      问题四、请问删除表的所有数据sql怎么写?
      可以通过删除(DROP)表达到数据删除目的。
      非分区表可以通过TRUNCATE TABLE table_name;语句清空表数据。
      分区表可以通过ALTER TABLE table_name DROP IF EXISTS PARTITION(分区名=‘具体分区值’)删除分区达到删除整个分区数据的目的。
      具体可参考 :
      https://help.aliyun.com/knowledge_detail/150534.html

      问题五、请问有什么办法快速查看项目空间下哪些表是分区表?

      select table_name  from information_schema.columns where is_partition_key = true group by table_name;

      问题六、新创建的工作空间。怎么不支持数据类型自动隐式转换呢?
      看下是否开启了MaxCompute2.0,可以关闭2.0之后可进行隐式转换。
      具体可参考:https://help.aliyun.com/document_detail/57792.html

      问题七、spark odps流式读取datahub数据,写到odps有没有参考文档或者代码?
      可参考:https://github.com/aliyun/MaxCompute-Spark/tree/master/spark-2.x/src/main/scala/com/aliyun/odps/spark/examples/streaming/datahub

      问题八、怎么将开发环境的表数据同步到生成环境的表中?

      insert into project.table  select * from project_dev.table;

      如果没有对生产环境表读写权限,对子账户需要授权grant role to $RAM:User;

      问题九、可以使用tableau连接MaxCompute吗?
      可以。具体可参考:
      https://help.aliyun.com/document_detail/115493.html

      问题十、 查询一个分区表 where条件是add_months('2020-06-01',-1) ,报错:is full scan with all partitions, please specify partition predicates. 怎么解决呢?
      可以通过explain命令查看SQL中的分区剪裁是否生效。
      具体可参考:https://help.aliyun.com/document_detail/58679.html

      欢迎扫码加入 MaxCompute开发者社区钉钉群,或点击 申请加入。

      image.png

      ]]>
      新品ROI涨322%,直播转化翻11倍,阿里云数据中台成了品牌们的一把手工程 Fri, 20 Jun 2025 02:20:33 +0800 前言:
      -更多关于数智化转型、数据中台内容请加入阿里云数据中台交流群—数智俱乐部 (文末扫描二维码或点此加入

      -阿里云数据中台官网 https://dp.alibaba.com/index

      (来源:数智俱乐部)

      从雾里看花到亲身体验,短短两个月,阿里云数据中台零售行业解决方案就让九阳“真香”了。

      消费者活跃度下降、线上线下数据割裂、业务增长面临瓶颈等问题,是不少零售企业目前面临的新挑战,九阳也不例外。

      以豆浆机起家的九阳创立于1994年,在过去20多年里积累了大量用户数据,只是这些原始数据既分散又碎片化,对于促进品牌的业务增量具有一定的局限性。

      在刚过去不久的天猫618,首次在大型活动中尝鲜阿里云数据中台零售行业解决方案的九阳,打了一场漂亮的仗。天猫618开场当天,九阳免洗豆浆机成交同比暴增230%,6月16日0点40分,天猫销售额突破2300万,同比超过去年全天,截至当晚23:30,成交额已经超过去年三天总和。

      值得一提的是,九阳Line Friends联名新品优选放大人群ROI提升了322%,养生壶品类ROI达到了6.85。“哪怕K150豆浆机这类高单价的产品,在对比行业基准纯拉新人群包ROI为1的情况下,它的ROI也达到了2.58。”九阳数字运营部总经理陈波表示,这背后离不开阿里云数据中台零售行业解决方案的助力。

      除了九阳,还有近百家零售企业在天猫618尝到了数智化的甜头,比如雅戈尔收藏加购ROI相较新客提升52.4%、奥普直播人群购买转化率是同期一般拉新人群的11倍。

      如何更有效地挖掘数据价值并加以使用,已经逐渐成为零售企业竞争的一大核心,数据中台的搭建也成了CEO和董事长们最重视的项目之一。

      找到精准人群

      有了数据的支撑,如何抓住年轻消费人群也变得有迹可循。

      九阳近几年一直在新品研发方面下功夫,贴合年轻人的喜好,打造出Line Friends联名款,早餐机、网红果汁杯等产品。其中不乏大爆款的产生,比如去年天猫双11,九阳和Line Friends联名暖杯垫这一单品,销售了近10万单。

      早前,九阳也曾在IP联名上走过弯路。“最早做的小黄人IP,产品不是很热门的品类,相对价格也比较高,IP购买人群中,尝鲜人群会占据很大一部分,所以不能设置太高的门槛。”陈波认为,随着现在年轻人审美和需求的改变,IP联名产品除了为用户提供功能性之外,还额外增加了情感认同感,同时也帮小家电跳出了产品设计同质化的圈子,“我们还是十分看好这个方向的。”

      吸取了原先的教训,去年7月,带着与Line Friends联名的新品,九阳再次杀向消费市场。这一次,市场的反应截然不同。在九阳人群结构中,IP产品中18-25岁的人群比九阳常规产品中占比多了16%,这意味着IP帮助九阳在获取年轻用户方面起到了明显的作用。
      image.png

      在迈出第一小步后,九阳又遇到了第二个问题:如何进一步把这些IP新品推向更多消费者,实现更大的增量。

      为此,在今年天猫618前,九阳通过阿里云数据中台零售行业解决方案核心产品之一Quick Audience,对品牌近两年自有信息进行分析运营管理,同时根据类目活跃度、消费行为特征等筛选逻辑,描绘出更为精准的消费人群,从而使整个营销链路、数据闭环能够更加完整

      而在深度理解消费者之后,九阳根据不同的人群制定了不同的营销方案,比如针对“A人群”(认知人群)高频触达,“I人群”(兴趣人群)中对于折扣敏感型、高价值人群,推出不同的营销策略,让其转化为“P人群”(购买人群),从而在减少营销成本的同时实现成交转化率的提升。

      “通过阿里云数据中台零售行业解决方案洞察到的消费人群,比我们原先自己洞察的人群投放回报率更高。”陈波列举了一串数据,Line Friends联名新品优选放大人群ROI提升了322%,养生壶这一品类ROI达到了6.85。即使高单价的K150豆浆机,在对比行业基准纯拉新人群包ROI为1的情况下,它的ROI也达到了2.58。

      品牌未来的核心资产

      在九阳之前,其实有不少品牌在数据中台建设这条路上行走已久,同时基于不同零售品牌的不同特点,数据也在赋能着更多不同场景。

      知名乳制品企业飞鹤去年便在阿里云的帮助下,建设数据中台。跟九阳相似,多年的品牌积累之下,飞鹤并不缺数据,但是由于分散且不完整,数据很难达到驱动业务增长的效果。
      image.png

      在阿里云的帮助下,飞鹤以数据中台为核心重塑了CRM系统,建立了新的以数据赋能的营销平台

      通过对信息的整理、分析、加工,来不断反哺前端业务。比如,精准的消费群体画像能够有效支撑品牌营销策略,而人群及其区域的权益偏好则可以为品牌制订区域促销提供有效参考。

      无独有偶,另一乳制品企业雅士利利用阿里云数据中台,结合自身业务特性,挖掘出5个业务场景:导购场景、门店场景、积分场景、营销活动场景、会员场景。

      数据中台的存在,帮雅士利更高效地打破原有界限,实现渠道之间、线上与线下的数据共通;更好地让其理解数据、运营数据,从而实现消费者人群化运营、营销精准化投放。

      2019年天猫双11,雅士利通过数据中台实现短期快速提效,营销提效同比去年增长92%。

      此外,今年疫情期间,红蜻蜓4000多家门店无法营业,但通过全员线上服务营销,7天内新设立了500多个粉丝群,实现日均销售额突破百万。逆势增长背后,核心业务上云,利用阿里云数据中台、业务中台等平台能力进行数智化改造是它的重要推动力。

      image.png

      “我们可以看到,像九阳这样,利用数据中台去激活老客、拉取新客,做营销提效只是第一步,数据中台完成之后还将不断迭代,全链路的数字化可以从消费者贯穿到新品研发,甚至再到供应链改造。”阿里云数据中台高级专家列文表示,这是阿里云数据中台能够为品牌数智化转型带来的核心服务之一。

      一把手工程

      数智化是未来,即使众多品牌都有了这个共识,但为何真正走在前面的仍是少数?

      列文分析,数据中台不是一个工具或者一套运营方法,企业想要真正搭建自己的数据中台,其实是一个中长期的过程,更重要的是,这还将倒逼着企业组织能力进行升级:“这是一个没有CEO或者董事会级别推动,几乎做不成的项目。”

      对此,陈波也深有感触。早在四五年前,九阳就成立了数据项目组,那时项目组里只有几名数据人员做着日常的数据维护,直到数据中台这两年在云栖大会及新零售场景中被反复提及,九阳总裁杨宁宁更是下定了决心,只是对于这一场大“变革”,她一直在寻找合适的时机。

      真正让九阳把建设数据中台提上日程,还是去年杨宁宁在与阿里巴巴集团副总裁肖利华(肖博)的一次深入探讨之后。“未来变革的抓手是数据化。”陈波说这是总裁最常挂在嘴边的一句话。去年开始,九阳启动对运用数据中台的需求研究调研,今年正式迈入了数据中台的上线开发工作。

      这次阿里云数据中台零售行业解决方案在天猫618的小试牛刀,收获颇丰,促使九阳对于数据中台的建设步伐变得愈发坚定。随着数智化探索不断深入,如今GMV变得似乎没这么重要了。

      “我们的关注度已经从单看GMV转移到了消费者资产的积累。”陈波解释,以前品牌做活动、做促销,转化完了就结束了,没有具象纬度去分析人群,更别提数据追踪再次触达转化,“现在品牌各个阶段的消费人群变得更为清晰,用户的精细化运营也变得越来越重要。”

      方法论则是阿里为九阳提供的另一大价值,在陈波看来,阿里有数据治理、数据运营的经验,这些数据思维方式让九阳可以更便捷有效地去将数据资产运用到新的业务上。

      不过,这也为企业带来了另一道难题:需要对每个员工数据化工具的使用能力提出更高要求。

      也正是看到了企业的这些痛点, 2018年下半年,阿里巴巴平台数据产品团队内测了数据分析师认证项目,并面向市场逐步开放,从而帮助企业和个人更好地认识数据、了解数据。据阿里巴巴平台数据产品资深专家逸客介绍,截止到7月2日,该项目已经培养超过3万名数据认证人才,其中,更是为946个品牌培养1737名内部数据分析师。

      “九阳有10个数据分析师已经通过了认证。”陈波透露,随着数据在企业发展中扮演着愈来愈重要的作用,九阳不仅成立了专门的数据部门,团队成员也拓展到了近40人,“我们正朝着把数据分析运营能力成为九阳全员的基础能力方向努力。”


      数据中台是企业数智化的新基建,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。目前正通过阿里云数据中台解决方案对外输出,包括零售金融互联网政务等领域,其中核心产品有:

      官方站点:
      数据中台官网 https://dp.alibaba.com
      数据中台钉钉群二维码2.jpg


      ]]>
      和你谈谈数据分析报告 Fri, 20 Jun 2025 02:20:33 +0800 前言:
      -更多关于数智化转型、数据中台内容请加入阿里云数据中台交流群—数智俱乐部 (文末扫描二维码或点此加入

      -阿里云数据中台官网 https://dp.alibaba.com/index

      (作者:数智从业者)

      在当今企业纷纷推动数字化运营的背景下,“No Data, No BB”成了职场人的口头禅。做一份好的数据分析报告,大到成为能否帮助企业做出正确的商业决策,小到成为能否说服老板获取业务资源的关键因素。因此做出一份高质量的数据分析报告是一个职场人必备的利器。

      有人说,数据分析报告,不就是一堆的饼图、柱状图、散点图放到PPT上吗?我们尝试从这个人人习以为常的操作中看看是否有不变的门道。

      注:本文中图表通过“阿里云 QuickBI”实现,中国首个入选Gartner魔力象限的BI产品

      表达主题决定了我们的图表形式

      决定分析报告图表形式的并不是拥有的数据是什么,而是你所需要表达的主题是什么。

      图1和图2是根据一份相同的数据,展现的2个不同的图表:
      image.png
      image.png

      上图可以发现对于相同的数据,因为我们所需表达的主题的差异,也将呈现完全不同的展现方式。图1表达的主题是爽肤水和沐浴露两个品类在不同城市的销量排名,图2主要表达的主题是在相同城市在两个不同的品类的销量差异。

      因此在下笔做分析报告之前,先仔细想好自己要表达的主题是什么。

      在进行了完整的分析后,要抵制住把所有向听众展示的冲动,而应该把所有注意力集中到需要表达的主题重点上来,因为这些才是听众所需要了解的信息。

      为了找到我们分析的主题,需要了解分析报告针对的对象。详细的了解听众的背景和观点,并思考清楚我们期望听众对我们的看法。在内容上,希望听众了解什么,了解了以后有什么行动。了解了听众和内容后,再确认数据的表达形式,只展现能支持主题的数据。

      整篇分析报告要能通过三分钟说得清楚,每页报告都能一句话概括清楚。

      不要放弃“标题”这个绝佳的位置

      有些图表的标题就和猜谜一样,例如:公司销售趋势、分公司销售分布情况。完全没有指出图表的重点,公司销售趋势是怎么样的?分公司销售分布又是如何?

      别把我们需要强调的重点当做秘密一样不肯透露,而应该把它放在图表最前面,减少听众误解的可能性,并让他们的注意力集中到我们所想强调的数据上。

      如下图,这张图到底是为了表达全量销售金额没有明显的增长呢?还是为了表达2月份销售金额断崖式下跌?还是为了表达其它什么主题呢。请在标题上明确的告诉听众。

      image.png

      处理“成分对比”的关系

      成分对比主要体现在对与一个整体的每个部分的百分比的对比。常常出现“份额”、“百分比”等词汇。成分对比通常使用饼图来展现:

      image.png

      饼图在使用中建议不超过6个部分,如果超过6个部分,可以把剩余部分归类到“其它”项中。另外由于人们看数据习惯顺时针看数据,因此可把最重要部分放到12点位置,并用对比度强烈的颜色突出显示。

      饼图主要在标识单一整体各部分比例,如果需要比较两个整体的成分时,重点考虑柱状图(图3)。因为如果使用饼图(图4)会导致读者视线需要在不同图表间来回移动:
      image.png
      image.png

      成分分析可能包括子成分分析,需要把整体的一部分再作为整体进行分析,这时可将饼图放在开始的地方,百分比的柱状图放在后面:
      image.png

      我们要尽可能少使用饼图,饼图占整体的比例一般不超过5%。尽量不使用3D饼图或甜甜圈饼图。永远记住,图表只是为了让我们听众更好理解我们的数据,而不是图表有多与众不同。

      处理“项目间对比”的关系

      项目间对比主要是比较不同项目间的情况。常常出现“排名”、“大小”等词汇。项目间对比通常使用条形图来展示:
      image.png

      我们对于条形图的顺序需要深思熟虑,如果天然是有序的则按天然的顺序,例如人生阶段婴儿、少年、青年、中年、老年。但是如果没有这种天然顺序,需要考虑什么顺序对于我们数据主题是最有意义的。

      根据我们所需要突出的主题,选择条形图的排序方式。展现条形图数值的方式包括刻度尺或在条形图上显示数字,可根据情况选择其中一种方式,但是不要两处都显示,多余容易导致图形的混乱。同时标识数字时,把小数点后的数值去掉,3%总是比3.1415%容易被听众记住。

      对于项目间对比有时也会通过柱状图来代替,但是条形图相较于柱状图有两点明细的优势:第一,减少听众与时间序列对比的混淆;第二,条形图有较大的空间填写各项目的名称。

      项目间对比,还可以通过背离式条形图,往往可以形象的将有利与不利的情况分离开来:

      image.png

      项目间还可能针对一个范围进行对比,这时可使用范围条形图:

      image.png

      当比较的项目由多个部分组成,可通过堆积条形图,必须将最重要的成分放在靠近基线的地方,因为只有这部分才可被准确度量:

      image.png

      处理“时间序列对比”的关系

      时间序列对比关心的是随时间变化的对比。常常出现“变化”、“增长”、“下降”等词汇。时间序列对比通常使用柱状图或折线图来展示,如果时间点不多时可以使用柱状图,如果时间点是很长一段时间范围使用折线图更为合适:
      image.png

      对于折线图,趋势线一定要比背景线粗。当存在同一张折线图存在多条折线时,需要将最关注的线加粗加亮。但是当出现非常多折线时,我们的折线图就会呈现出“方便面式”图表,往往导致图表混乱。如下图:

      image.png

      解决“方便面式”图表的方式可通过将折线图拆分到不同的小的折线图中,虽然图表变多了,但是所需要表达的主题也能更加清晰:
      image.png

      在时间序列对比中,可通过箭头、线条、阴影等方式强调数据的某一部分,将听众的注意力集中到你所期待关注的点上:
      image.png

      同样,时间序列对比也可以通过刻度的正负来区分正面情况和负面情况:

      image.png

      我们常常在时间序列中,可能包括实际值和预计值,可通过将实际值设置为实线,将预计值设置为虚线的方式:
      image.png

      当一个折线图的数值,是可通过一个公式生成的,可将公式中的计算因子分别拆分到计算树中,使听众可清晰看到每部分计算因子的变化情况:
      image.png

      如果只有两个时间点,可展现两组数据之间各维度的提升和降低的差异,形成斜率图,连接的线条可以直观的感受到提升与降低的程度:
      image.png

      不同的刻度基线会产出完全相反的数据主题解读。看以下2张图,同样都是对杭州房价的描述:
      image.png

      同样的数据,产出的图表给人截然不同的感觉。那么我们到底应该如何定义刻度呢。其实关键取决于我们对于变化的理解,100块钱对于房价来说可能微不足道,但是对于动车票价格可能就是至关重要。我们应该选择一个刻度,能准确反映对变化重要性的理解。如果通过使用不合适的刻度基线来误导听众,只要有辨别能力的听众都能发现问题,那么我们的整套言论和信誉就会被唾弃。

      处理“频率分布对比”的关系

      频率分布对比表现的是数据分布范围情况。常常出现“范围”、“密度”、“分布”等词汇。频率分布对比通常使用柱状图或折线图来展示,当比较范围数量较多时可使用折线图,较少时可通过柱状图:
      image.png

      频率分布的范围大小非常重要,既不能太大也不能太小,建议5到20个分组。不同分组的大小应相同,否则会造成数据扭曲。

      对于即需要展示频率分布,又需要进行项目间对比,可将分布柱状图进行组合,其中一个分布柱状图作为另一个的背景图:
      image.png

      处理“相关性对比”的关系

      相关性对比表现的是不同变量之间的关系。常常出现“与XX有关”、“随XX增长”等词汇。相关性对比通常使用散点图或双条形图来展示。如下图:

      image.png

      在双条形图中,我们将独立变量按顺序排在左边,而把对比值放在右边,如果期望模式与实际模式一致时,右边的条形图就会变成左边的条形图的镜像,如下面左图。当关系不符合预期时,两组条形图则会发生偏离,如下面右图:
      image.png
      image.png

      处理“多重对比”关系

      对比可能不仅仅是一种对比关系,有可能是结合上文所说的成分、项目间、时间序列、频率分布、相关性对比中的一种或多种组合而成,我们称之为“多重对比”。例如“销售额在过去10年内稳步增长,但利润却没有同步增长”,这案例第一部分“销售额在过去10年内稳步增长”属于“时间序列对比”,第二部分“但利润却没有同步增长”属于项目间对比。对于这种情况,我们需要确定哪一种关系是主要的,哪一种是次要的。案例中,第一部分随时间变化是主要的,而销售额与利润项目间对比是次要的,因此最好选择以时间变化对比的折线图,并为每一个项目画一条支线的方式实现,如下图:

      image.png

      不仅仅只有图表

      当然,对于数据分析报告,可定量信息我们可以通过图表构建,但是对于不可定量的信息,我们可以通过一些几何图形形成概念性视觉图像,或通过一些日常事务作视觉比喻。例如说到目标,可以形成高山这样的视觉图像。这需要发挥自己的想象力,平时多留意写优秀的海报广告,在生活中寻找灵感,在报告中能够做到与听众产生共鸣的效果。

      消除认知负荷

      听众在接受我们分析报告信息时,需要消耗脑力去学习新知识,脑力是有限的,因此需要消除听众无关紧要的脑力消耗。造成无关紧要脑力消耗最大的问题就是“杂乱”,因此消除杂乱是数据分析报告需要重点关注的。

      通过将文字从原来居中对齐调为左对齐,进行相关的无关数据的淡化的处理,能减少听众的认知负荷,把关注点转移到我们的重点上::

      image.png

      image.png

      通过将网格线消除、标记点消除、金额度量转换、直接标记数据等手段降低认知负荷,右图是修改后的图表:

      image.png

      通过消除一些干扰,能突出我们所需要表达的重点。所有的数据不是相同重要,消除不需要关注的元素,或将不直接影响内容的元素融入背景。去掉这个东西会有什么变化?如果不会,那么就去掉吧。

      同时要突出我们需要吸引听众实现的地方。在文字中可通过加粗、颜色、斜体、大小、空间隔离、下划线等手段突出文字关键词。在图表中主要通过颜色、大小突出需要强调的内容。在使用颜色时需慎重选择,不能在一张图中有太多颜色,造成视觉干扰;可以使用颜色的不同饱和度来强调数据;根据分析报告背景,可选择对于的互补色来做内容的突出强调。

      讲好分析报告

      分析报告做好了,还需要以更好的语言表达方式呈现给听众。可以把分析报告当做一个故事来说,能更加引人入胜。

      首先对整个背景做设定,接着介绍什么因素上下文驱动情节的演进,举例说明发生了什么样的冲突,基于这些冲突有哪些假设,再基于这些假设如何做数据验证,最后通过什么方式带来什么解决方案。

      为了保证整个分析报告的逻辑清晰,可以构建类似金字塔的逻辑结构,以某一个中心论点为塔尖,在其以下分支出不同论点的数据分析支撑。让听众对我们的分析报告有个清晰的逻辑结构。

      最后,希望每个职场人都能用数据分析报告打开一个新的天地。广阔数据天地,大有可为。

      工欲善其事必先利其器,BI工具,推荐“QuickBI”,众多可视化组件拖拖拽拽就完成了,人人都是数据分析师。


      数据中台是企业数智化的新基建,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。目前正通过阿里云数据中台解决方案对外输出,包括零售金融互联网政务等领域,其中核心产品有:

      官方站点:
      数据中台官网 https://dp.alibaba.com
      数据中台钉钉群二维码2.jpg


      ]]>
      使用Java带你打造一款简单的英语学习系统 Fri, 20 Jun 2025 02:20:33 +0800 【一、项目背景】

      随着移动互联网的发展,英语学习系统能结构化的组织海量资料。针对用户个性需求,有的放矢地呈现给用户,从而为英语学习者提供便利,提升他们的学习效率。

      【二、项目目标】

      1. 实现美观的界面,添加需要的组件。
      2. 能够基本实现改变字体,颜色,背景,页面切换功能。
      3. java读取txt文件,简化代码。

      【三、项目实施】

      使用eclipse软件开发,先上效果图,如下图所示。可以看到在界面上有可以改变字体、颜色、设置选项的菜单栏,页面切换的功能。

      使用Java带你打造一款简单的英语学习系统
      接下来,小编带大家进行具体的实现,具体的实现步骤如下。

      【四、实现步骤】

      一、首先实现窗体界面

      具体的代码实现过程如下:

      public static void main(String[] args){

      // TODO Auto-generated method stub
          EnglishSystem es =new EnglishSystem();
          es.setTitle("英语学习系统");
          es.setSize(750, 600);
          es.setVisible(true);
          es.setResizable(false);
          es.setLocationRelativeTo(null);
      

      }
      使用new关键字创建EnglishSystem类;

      setTitle表示设置界面的标题;

      setSize(宽,高)表示窗体大小;

      setVisible(true或false)表示窗体是否可见;

      setResizable(true或false)表示窗体是否可以由用户调整大小;

      setLocationRelativeTo()表示设置窗口相对于指定组件的位置。

      二、实现菜单栏

      使用Java带你打造一款简单的英语学习系统

      1. 创建JFrame实例、JPanel面板,然后把面板添加到JFrame中。
      2. 创建JMenuBar菜单栏对象,JMenu在创建菜单对象,将菜单对象添加到菜单栏对象中。
      3. 将JMenuItem菜单项添加到JMenu中。

      public class EnglishSystem extends JFrame {

      private JPanel panel01 = new JPanel();//菜单栏
      private JMenuBar jb = new JMenuBar();
      private JMenu menu01 = new JMenu("字体");
      private JMenuItem item01 = new JMenuItem("宋体");
      private JMenuItem item02 = new JMenuItem("黑体");

      private JMenu menu02 = new JMenu("颜色");
      private JMenuItem item03 = new JMenuItem("玫红色");
      private JMenuItem item04 = new JMenuItem("蓝色");
      private JMenuItem item05 = new JMenuItem("绿色");
      private JMenuItem item06 = new JMenuItem("橘色");
      private JMenuItem item07 = new JMenuItem("黑色");

      private JMenu menu03 = new JMenu("设置");
      private JMenuItem item08 = new JMenuItem("换壁纸");
      private JMenuItem item09 = new JMenuItem("退出");

      1. 实现单词区

      private JPanel panel03 = new JPanel();//单词显示
      private static JTextArea text01 = new JTextArea(30,89);

      1. 实现上下页切换

      private JPanel panel04 = new JPanel();
      private JButton btn_next = new JButton("下一页");
      private JButton btn_last = new JButton("上一页");

      1. 当前背景的图片

      private int photoNum=1;//背景图数
      private JPanel imagePanel;
      private ImageIcon bg= new ImageIcon("photo//photo"+photoNum+".png");//背景图
      private JLabel label = new JLabel(bg);

      1. EnglishSystem类构造函数:构造这个函数主要是实现界面的设计,添加组件。

      EnglishSystem(){

      jb.add(menu01);
      jb.add(menu02);
      jb.add(menu03);
      
      menu01.add(item01);
      menu01.add(item02);
      
      menu02.add(item03);
      menu02.add(item04);
      menu02.add(item05);
      menu02.add(item06);
      menu02.add(item07);
      
      menu03.add(item08);
      menu03.add(item09);
      panel01.add(jb);
      this.add(panel01);
      this.setJMenuBar(jb);
      
      panel03.add(text01);
      text01.setText(str1);
      text01.setEditable(false);
      text01.setLineWrap(true);
      text01.setWrapStyleWord(true);
      panel03.setBorder(new TitledBorder("单词区"));
      this.add(panel03,BorderLayout.CENTER);

      text01.setFont(new Font("黑体",Font.PLAIN,14));

      1. 将字体、颜色、背景添加到JMenuBar菜单栏中,字体里面的菜单项如黑体、宋体添加到菜单中。其他颜色、背景添加组件也一样!

      panel04.add(btn_last);

      panel04.add(btn_next);
      this.add(panel04,BorderLayout.SOUTH);
      
      text01.setOpaque(false);
      panel01.setOpaque(false);
      panel03.setOpaque(false);
      panel04.setOpaque(false);
      
       label.setBounds(0,0,bg.getIconWidth(),bg.getIconHeight());//设置边界
          imagePanel=(JPanel)this.getContentPane();//获取窗体的内容面板
          imagePanel.setOpaque(false);//设置透明
      this.getLayeredPane().add(label,new Integer(Integer.MIN_VALUE));
      1. 定义事件处理类,实现事件监听器

      private MyListener my = new MyListener();

      1. 在EnglishSystem构造函数中给指定组件添加监听

      item01.addActionListener(my);
      item02.addActionListener(my);
      item03.addActionListener(my);
      item04.addActionListener(my);
      item05.addActionListener(my);
      item06.addActionListener(my);
      item07.addActionListener(my);
      item08.addActionListener(my);
      item09.addActionListener(my);

      btn_next.addActionListener(my);
      btn_last.addActionListener(my);

      1. 添加事件监听器MyListener(自己命名)。

      private class MyListener implements ActionListener{

      @Override
      public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
      
        if(e.getSource()==item01){//宋体
          text01.setFont(new Font("宋体",Font.PLAIN,14));
        }  
          if(e.getSource()==item02){//黑体
            text01.setFont(new Font("黑体",Font.PLAIN,14));
          }
          if(e.getSource()==item03){//玫红色
            text01.setForeground(new Color(255,0,255));
          }
          if(e.getSource()==item04){//蓝色
               text01.setForeground(Color.blue);
          }
          if(e.getSource()==item05){//绿色
               text01.setForeground(new Color(0,100,0));
          }
          if(e.getSource()==item06){//橘色
               text01.setForeground(new Color(255,140,0));
          }
          if(e.getSource()==item07){//黑色
               text01.setForeground(Color.BLACK);
      }

      if(e.getSource()==item08){//换壁纸
      photoNum++;
      if(photoNum>=6){
      photoNum=1;
      }
      label.setIcon(new ImageIcon("photo//photo"+photoNum+".png"));
      }
      if(e.getSource()==item09){//退出
      dispose();
      }
      if(e.getSource()==btn_next){//下一页
      if(papeNumpapeNum++;
      btn_last.setEnabled(true);
      btn_next.setEnabled(true);
      }
      if(papeNum==s.length){
      btn_last.setEnabled(true);
      btn_next.setEnabled(false);
      }
      }
      if(e.getSource()==btn_last){//上一页
      if(papeNum>1){//不是第一页
      papeNum--;
      btn_last.setEnabled(true);
      btn_next.setEnabled(true);
      }
      if(papeNum==1){
      btn_last.setEnabled(false);
      btn_next.setEnabled(true);
      }
      }

      1. 程序中显示文字是以String数组形式存储,这种方式比较方便易懂,但却使得代码较多。因此,在文字较多情况下,应考虑以txt文档形式存储故事文字,在程序中读取文档内容,以显示在窗口中。

      读取Txt文件:

      File file = new File(s[papeNum-1]);

        String str1 = getFileContent(file);
        text01.setText(str1);
      1. 定义一个字符串数组

      private String[] s = new String[]{"resource//s01.txt","resource//s02.txt","resource//s0 3.txt","resource//s04.txt","resource//s05.txt","resource//s06. txt","resource//s07.txt","resource//s08.txt","resource//s09.tx t","resource//s10.txt","resource//s11.txt","resource//s12.txt", "resource//s13.txt","resource//s14.txt"};
      private int papeNum=1;//页数

      1. 在getFileContent函数获取文件内容

      private String getFileContent(File file) {//获取文件内容

         BufferedReader br = null;
         StringBuffer sb = new StringBuffer();
         try {
          br = new BufferedReader(new FileReader(file));
          String hasRead = null;
          while ((hasRead = br.readLine()) != null) {
           sb.append(hasRead + "n");
          }
         } catch (Exception e) {
      
         } finally {
          if (br != null) {
           try {
            br.close();
           } catch (IOException e) {
      
           }
          }
         }
         return sb.toString();

      }
      以上用到的组件主要是Java Swing图形界面开发:

      1. Swing是JAVA的基础类的一部分。
      2. Swing包括了图形用户界面(GUI)器件如:文本框,按钮,分隔窗格和表。
      3. Swing 提供了许多比 AWT 更好的屏幕显示元素,使用纯 Java 实现,能够更好的兼容跨平台运行。

      【五、总结】

      1. 主要介绍了JPanel、JButton、JLabel、JTextArea、JMenu、JMenuItem等组件的基本使用,以及相应的事件处理。
      2. 事件处理函数的添加,难点是运用理解构造函数、内部类的创建。
      3. 如果需要本文源码,请在公众号后台回复“英语系统”四个字获取。

      看完本文有收获?请转发分享给更多的人

      IT共享之家

      想学习更多Python网络爬虫与数据挖掘知识,可前往专业网站:http://pdcfighting.com/

      ]]>
      用阿里云服务器ecs之建站方法汇总(云服务器建站+心选建站+定制建站) Fri, 20 Jun 2025 02:20:33 +0800

      ]]>
      饿了么4年 + 阿里2年:研发路上的一些总结与思考 Fri, 20 Jun 2025 02:20:33 +0800 我是在2014年入职饿了么,从前端和PHP一直做到后端架构和团队,从2014年到2017年陆续负责过公司客服、销售、代理商、支付、清结算、订单这些业务的产研与团队;2018年从业务研发团队抽身,6个人组起一个小组投身机器学习,试图结合实际的业务场景通过技术改造业务;2019年回归到平台(中台)研发,负责交易、金融、营销三个中台的研发和团队工作。基于我在饿了么的4年和阿里巴巴的2年研发经历,从技术、业务和架构层面分享一些我的思考。

      一、技术层面

      对开发同学而言,技术是立身之本,虽然往往面试造火箭入职拧螺丝,但技术就是你从业的的基石。不管是基本的动手能力还是问题分析能力,包括你的思维逻辑乃至对事物认知的思路,技术思维都会时刻影响你,最明显的影响就是当你面对无数个问题的钉子时,技术是不是你最顺手的那把锤子。

      技术上我比较关注的几个层面:

      基本功(语言、编码这个层面,主要是动手能力)
      大型分布式系统的实战经验(RPC、SOA、MySQL、Redis、MQ)
      项目(DB设计、API契约、DDD抽象、链路设计、项目风险把控)
      稳定性(可用 & 资损)

      1、 稳定性

      稳定性是一个先有意识再有能力的事儿,记得2015年初,张雪峰加入饿了么担任CTO之后,从他嘴里最常听到的一句话就是“研发要对生产环境有敬畏”。

      2014年下半年,各方人马都开始杀入外卖市场,饿了么启动百城计划进行业务扩张,短时间内从10+城市覆盖到100+城市,日订单量也很快从10W上涨到100W。业务井喷的同时,技术还没有做好足够的准备,我印象中2014年下半年近乎每天中午交易量都有新高,但同时也伴随着系统开始宕机、限流扩容、紧急调优、客服爆线、技术加班熬夜。

      我曾在新乡的客服中心看到有的客服同学突然崩溃,耳机直接摔下来离开工位,因为每天会接收到大量用户的来电责问,就在那一刻其实你才会清晰且直观的感受到:你在编辑器的一行代码,你在服务器的一次发布,会对现实世界很多活生生的人有直接影响,你会突然意识到你的工作比你之前以为的要重要且有意义。
      所谓研发要对生产环境有敬畏,就是你知道你的作品会对别人产生不好的影响,你会为不好的结果感到惭愧与内疚,这就产生了敬畏。

      应急处理有一个基本原则:“以业务影响最小为主,优先恢复为核心目的,不要纠结手段和根因。”

      image.png
      别把你的懊悔、决心、稳定性的思考、各种奇妙的idea以及执行力体现在事故复盘会上,系统的安全生产和火灾一样,事前才有意义。

      2、 链路设计

      大部分产研缺少全链路的视角,往往看到的是自己负责的点,但是对于一条线乃至整个面是看不到的,也没有机会去思考这些,而对于一些大项目和长链路系统而言,这是致命的。

      对你所负责的系统,它关键的上下游、核心业务的链路一定要熟悉,包括数据、接口(调用、功能、逻辑)、各种异常的处理和特殊的设计。

      能帮你达成这一目的的最简单的办法就是画图、画图、画图!重要的结论说三遍,一定要自己能把系统的大图画出来,然后做到可以根据大图随意放大和缩小。放大到就是将链路画到业务场景里突出业务逻辑和上下游交互,缩小就是细到某一次调用的处理逻辑大致是怎样,数据是怎么变化。经常画图,不用纠结形式和标准,重要的是形成自己理解系统的一个框架,一个自己的思维方式,需要的时候可以随时拿出来用。

      日常不管是聊需求还是做系统设计,习惯性的把图画出来,就达成了一半了。剩下的一半就要看着图去找想、去找问题。放两个双十一猫晚在公开渠道的大图,可以找找感觉,自己画的话一定要钻到服务、接口和数据上,类似我上面说的你有一个放大镜,随时能放大也能缩小。
      image.png
      image.png

      3、 技术债务

      永远不要骗自己说,现在为了这个需求先挖一个坑,过一段时间再来填(重构 or 重做)。
      技术债务和金融债务一样,它必然存在,并且会像无赖一样一直赖着,隔三差五会爆发一下。随着时间的推移和业务的发展,你会发现架构越来越混乱,不同系统的领域边界越来越模糊,系统和需求与组织关系的映射越来越复杂,服务内编码风控越来越不一致,重复的轮子一个接一个隐蔽的出现。

      “太忙了没时间梳理哪些问题”、“改那些问题需要上下游一起改,费时费力,推不动”、“现在还没出问题,而且正在整理了,别催”。其实通常情况下,技术同学多少都有点代码洁癖,有很多问题不处理不是主观的原因,而是客观上因为精力、时间、ROI等因素,往往要等问题真的爆发后,大家才能狠下心来去处理那些问题 。

      我以前处理技术债务的思路,是要有一个检查清单,我会定期的复盘所有的系统,并且结合自己团队和其他团队的事故去全量扫雷。系统本身是一个平衡的产物,是时间、功能、风险、未来可能性等几个方向平衡的结果。所以技术债务对于研发同学的考验,不在于你怎么在日常工作中保证系统技术债为0,而是你要评估清楚在有限的资源和时间下,哪些问题是刻不容缓的,哪些问题是可以往后放的。

      很难想象一个没有技术追求的团队能开发出一个健壮的、可维护性好、可扩展性好的系统。相反,这种业务代码的堆砌,从短期看也许是“较快”实现了业务需求,但是从长远来看,这种烂系统的增加会极大的阻碍业务的发展,形成一个个的黑洞应用,而工程师被裹挟在业务需求和烂系统之间,疲于应对,心力交瘁。这种将就将导致系统腐化堕落,技术债越垒越高,丑陋的代码疯狂滋长,像肿瘤一样消耗你所有的能量,最后还要你的命。

      4、警惕大项目

      并不是所有人都有能力操盘大项目,可以平衡好交付压力、上线质量、产品逻辑以及时间窗口,这是一个非常有挑战的工作,需要纯粹的技术能力之外的很多软性能力来辅助,比如组织的沟通协作能力、向上要权要责的能力、平衡产品业务期望的的能力、突发情况应急转变的能力等。越大的项目对于Owner的要求也越高,真能把大项目做好不怎么留大坑的少之又少。

      大项目从启动到立项所用的时间很多时候是远超项目实际的开发周期的,项目的顺利推进需要妥协,但项目的价值成功需要坚持。很多项目失败其实在做的过程中,方方面面不断妥协,最后做出来的东西已经远离了最开始想象的样子。

      二、业务层面

      除了技术之外,研发同学对业务层面也需要提升认知与重视。
      对于研发而言,业务就像是外语,你不理解业务就好似人在异国,与周围的环境格格不入,并且容易吃亏!相比产品、业务、运营等其他工种,技术更喜欢和技术打交道,业务在大多数同学眼中是混沌且缺少秩序的,没有技术那样清晰的实现路径和稳定共识的知识结构。并且技术相对容易证伪,而业务往往就是不停的尝试,研发都讨厌“不确定性”。但是在一个庞大的组织里想把技术做好,就必然要与业务打交道,毕竟业务才是一家公司存在的核心价值。

      1、基于业务规划做技术规划

      很多同学习惯于把计划=规划。阿里是一家尊重技术的商业公司,所以你看所有的团队都在谈业务、规划里是业务规划、周报里是业务项目、汇报里是业务成果、晋升的时候也要突出你的“战功”。相比技术本身,大家更关注技术改变了什么,在业务部分聊技术团队如何做规划的原因就在于此,这是公司运营的的起点(目的),延伸出来才有具体的技术规划和组织设计作为解决方案。

      深刻理解业务并设计战略,拆解战役与项目,通过组织和各种机制确保项目的执行与落地,最终拿到业务结果,这是一个大公司的标准战略执行方式。研发同学做技术规划以及团队规划的时候,一定要考虑到你所在环境,公司今年要主打什么、所在大部门的目标是什么、对口的产品和业务现状如何、纯粹的技术迭代在业务上的好处在哪儿、另外团队目前有哪些不可抗力、或者影响规划推进的阻力。

      很多同学做规划的时候会习惯性按照这个思路进行:① 总结现状(痛点)=> ② 对应的解决方案和策略 => ③ 展望未来。有时候②和③的顺序会反过来。很多时候大家发现最终的部门规划和自己做的规划没什么关联,不知道怎么往那个方向用力,或者干脆继续按照自己的计划先走着。

      对大部分同学,我建议规划要实在。做技术规划前,找你的周边研发团队聊一下,找你对口的产品、运营聊一下,知道他们的目标是什么,知道公司几个重点是什么,然后结合你们目前的痛点,在现在和未来之前找平衡、找现在和未来都有收益的那部分。
      规划有一些很硬的内容,这个目标解决什么问题、怎么确定它解决了以及解决的好不好,好的结果谁认可等。规划一定要有重点,没有重点的那不叫规划,那是你后面的日程计划。很多同学对做规划不投入,也有自己的想法,比如公司业务或者战略变化太快、组织调整太频繁,下半年在哪个团队工作甚至做什么都不一定。不否认变化的频繁以及对规划的影响,但是如果一直这样思考,你永远无法从变化中获得价值,因为你一直在置身事外。

      2、研发要比产品还懂业务

      研发要比产品还懂业务。雷军说过:“永远不要试图用战术上的勤奋,去掩盖你战略上的懒惰。”对于研发同学,你要比业务同学更懂业务才能找到技术与业务平衡的空间。对大部分同学而言,常常是只熟悉自己负责的系统,但是对于想要更大空间和更多成长的同学,我可以给出明确的结论:只熟悉自己负责的系统是不够的。

      首先,不同的人对熟悉的定义不一样。对于你负责、你贡献代码、你进行设计并且完成需求交付的系统,单是熟悉远不够,你不仅要知道它的前世今生,思考它的一路成长,纠结它的未来发展,同时还要清楚它的风险与隐患,它的生与死。

      基于你最清楚的核心系统,由它开始做业务场景上的外延,以此了解你的上下游,并且能做到结合业务场景去挖掘。从业务的角度、从产品的链路、从技术的调优和隐患多个视角去切,让自己的设计维度与视角不断拉升,这样你有把握或者有掌控力的范围会越来越大,未来才会有更多的机会。

      三、管理层面

      团队是一个宏观与微观并存的事务,宏观上我们说组织、讲战略、定规划、要排兵布阵,微观上我们关注沟通、成长、情绪。大部分同学之所以在微观上受挫就是因为没能把握到宏观的节奏,公司是一个盈利组织,技术中心是一个成本部门,技术中心之所以会有某一个组织那么一定是:“公司期望这个组织解决某一类问题,并且解决到一定程度。”

      所以在这里你要理解一个关键词,“结果和KPI”并不取决于你怎么定义它,而是给你下放目标的组织与管理者对你的期望是怎样的,你们的GAP往往未必是结果的差别,而是期望的落差。

      1. 拥抱变化

      其实大多数时候不需要你去拥抱,变化会突如其来的抱住你,勒紧你的脖子让你有那么一瞬间觉得呼吸困难。互联网公司之所以变化快,很大程度取决于它的业务属性,相比传统公司能更快更清晰的感受到与市场的契合程度并且及时调整策略,所以没有时间和信息上的延迟与滞后。

      结合这几年的经历,到最近两年加入阿里,一直说拥抱变化感觉就比较自然了。核心的体悟有两个:

      (1)对业务的发展和环境的变化要敏感,如果能在在变化到来之前主动发起变化,那么化被动为主动是最好的。即使不能,也要清晰的去感受和思考变化背后的动力在哪儿,去给找到关键的发力点,让自己可以适应变化。

      (2)变化带来的工作内容的调整、汇报线的调整、团队的变化等,不管好坏都是在一个时间段内相对的,而不是一个工作生涯中绝对的。不可能事事如意的情况下,调整自己的心态,把自己从情绪中摘出来更多关注事情会是一个好的选择。

      2. 加人不能解决问题

      即使嘴上再怎么说“不能”,但是动作会很诚实,依然会尽可能多地要HC,更多的拼命的把更多“核心”的系统建建设在你的职责范围内。
      另外可以辩证的看类似于有效灰度,你可以看一下你有没有“有效加人”,一些TL不关注新人的Landing,相当于只盯数量不盯质量,最后结果肯定是一塌糊涂的。

      从绝对理论的角度加人肯定是有帮助的,你的资源变多了,周转的空间和操作的余地都丰富了。但是从经验看,大部分情况下加人没有产生最终的价值变化(项目的结果、业务的成败)。因为系统的开发、项目的推进并不是单纯的资源堆砌,1000人日的系统哪是1000个人做一天就做出来的。真实的世界上,我们往往不是败在资源的使用量上,而是资源的使用方向和使用效率上,但是因为加人这件事儿上最容易的、最无脑的,所以大多数情况下也最被优先选择。

      3. 有意识地向上管理

      这个问题源于过去经历的两个点,一是我经历了无数次的组织关系调整,我发现不管是我自己还是我团队的同学,大家相比于自己做什么以及带不带人、带多少人外,更关注的是自己的汇报线。自己汇报给谁,谁是我老板,我和他处不处的来,他能不能让我有提高、有成长。二是很多同学,尤其是P5、P6和少部分P7的同学,会对与老板的相处关系有困惑。
      基于这两个点我把向上管理作为一个单独的话题加了进来,先说结论:要!要!要!必须要!一定要!

      连马老师都说员工离职的三大要素之一就是和老板处不来,你怎么还能心安理得的忽略它。如果大家对于向上管理还停留在服从甚至谄媚的态度来处理你们的关系,我只能说亲,你真是硬生生把这么高大上的词儿给解释的特别的接地气。我没有系统的学过向上管理,也没有体系化的看过这方面的书,所以我只说一下自己的理解。

      个体在一个组织里想得到报酬和收益,基本的方法就是促进组织的成长与提高并且同步提高自己,这样就可以从中分得自己那份收益。这就要求你产出的结果是要对组织有正向溢出的,但是这个方向与标准并不是所有人都清楚或者能准确的把握到。经常有绩效差的同学很沮丧甚至抱怨的说,我自己经常加班,甚至是部门走的最晚的,周末也要处理工作等。先不讨论复杂的背景上下文,如果命中上面这一条的,我先给你个忠告:除了按件计费的工厂,其它任何地方体力上的付出与最终结果都没有明显的直接关系。就像你上学的时候一定有那么几个别人家的孩子,要么就是特别努力学习特别好,要么就是看上去每天和你一起玩,但是成绩总是碾压你。从学校到社会,这个现象并没有消失,别迷信加班和体力上的付出,大多数人只要能不去思考,在体力上愿意做出的付出,远超你我的想象。

      与老板的相处和沟通,本质上是为了达成一致的目标和互相认可的结果。这是一个非常关键的初衷,我有时候开玩笑和团队的同学聊,说你们要好好看看我老板到底想干嘛,这样你们做出来,我好去汇报。方向、节奏、结果的对焦对于工作的展开和拿成绩是至关重要的,同时你要从他身上获取更多的信息以便于自己的判断决策和学习,不断从老板身上吸取养分。

      在一些环境中,体力上的付出是必须的,但是仅有体力上的付出最终只能感动你自己,你的团队并不想每天陪你加班到11点或者发布到凌晨2点,更没有兴趣凌晨1点半还拉个电话会讨论方案。所以一定要做好“期望管理”,你老板对你的期望、对项目的期望、你对他给予你空间和支持的期望,大家一定要对焦清楚并且目标一致。

      四、架构层面

      还有一点我觉得也很重要,就是在架构层面,包括业务架构、技术选型和细节实现上,要有清醒的认知。

      1. 最关键的是定义问题

      爱因斯坦说过:“提出问题比解决问题更重要!”定义问题是个脑力活,解决问题是个体力活。大家往往习惯于看到一个问题就冲上去锤它,从概率上来讲,很大可能会陷入一个解决问题的黑洞,就是你不停地在解决问题,但是最终你的情况没有变好。

      当你面临一个困难或者一个情况时,首先比较重要的是定义问题,这到底是要解决什么、解决了有什么好处、怎么确定解决了。其次是定义结构,这个问题的关键点组成,你对应的解法是怎样的,这其中得失要怎么权衡轻重,并且最终解决的效果如何贯穿和透传,由点及面。

      一个团队可以不停歇的埋头干,但是未必会干出成绩。大部分习惯罗列面对的问题,但是对这些问题并没有做一个全局的分析和梳理,其实最难的就在“找问题”上。

      2. 问题的本质没那么高深

      有时我们做一个项目,可能有一个产品需求,大家看完觉得不好做或者做不了,因为系统现在不支持,改造成本太高,并且还伴有很多不确定的技术风险。相信很少有人在这种情况下会无脑的要求加人、加工期来解决这个问题。大多数情况下我们会看有没有捷径或者其他方案,让产品效果达成,哪怕技术实现脏一些、绕一些。其实这时候横向纵向多挖一下或者多问几个问题,有可能就会有不一样的答案。这个需求在解决用户什么问题,目前这个解决方案是不是业务(产品、技术)上唯一的,这个解决方案会带来哪些成本和新的问题,目前正在推进的其他项目和这个问题会不会有关联,有没有其他团队也在解决类似的问题或者曾经解决过。

      3. 达成目标

      在工作中小到聊定一个API契约、中到上线一个需求、大到完成一次晋升,所有的事情都是有成功的方法的。找出短板、设定计划、抗住挫折、反复训练、根据反馈调优,就可以解决任何问题。《债务危机》这本书里总结了一个五步成功法,很有意思:
      image.png
      著名的大数学家波利亚有一本名著《怎样解题》,其中给了一个四步解题法,可能站在研发的角度看会更有感觉:

      image.png

      4. 持续学习才是根本

      时代在持续发展和变化,现在正是最波澜壮阔的年代,这样的环境下,不管当前如何积累,都有可能随着发展的变化在短时间跌落谷底。公司能发展一定是在某一个时期内非常契合环境的要求,但随着时间的变化,如果它的变化不能跟上来,那么也只会被时代抛弃。正所谓让你成功的,最终也将让你失败,比如柯达、诺基亚不能幸免,个体也难逃这样的规律。

      这样的情况下,持续的学习和改变自身的能力才是研发同学最大、也是最强的优势。技术本身的日新月异要求你持续学习,同样的习惯放射到各个领域上,才会慢慢的取长补短,优化自身,所以如果说研发同学最需要什么,我认为持续的学习能力是最关键的。

      正如饿了么创始人张旭豪在之前接受采访时有一句话,让我很难忘:最重要的是选择,最困难的是坚持。

      作者:石佳宁

      ]]>
      SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(工具部署) Fri, 20 Jun 2025 02:20:33 +0800 前言

      上一篇我们介绍了从 IDE 插件内介绍了如何进行应用部署的方式,除此之外,目前 EDAS 还支持了额外的工具对其他场景进行覆盖,这一篇内容主要就是介绍 EDAS 上围绕部署的工具体系。

      第一篇:《SpringCloud 应用在 Kubernetes 上的最佳实践 — 开发篇》
      第二篇:《SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(开发部署)》

      IDE 插件中进行部署

      因为 IDE 是离开发人员的代码最近的工具,所以 IDE 插件中的部署能力也是专门为开发人员提供的部署工具,他的特点就是速度快、使用简单,同时也覆盖了 ECS 集群与 Kubernetes 集群中的 War/Jar 、以及自定义镜像的部署方式。具体使用方式,我们都已经整理成了官方文档,请在 EDAS 的官方帮助文档中,查看《使用 Cloud Toolkit 快速部署应用至EDAS》章节。

      不过对于线上的应用而言,如果随便一个开发人员都能进行随意的变更,这是一件很不安全的事情。EDAS 在命名空间设计的时候,也考虑到了这个问题,解决的办法就是 EDAS 上的命名空间,其用途是用来隔离环境之间的服务与配置用的。可以理解成通常意义上的环境,如:开发、测试、生产等。为避免用户在 IDE 插件中误将线上环境进行变更,我们对命名空间加入了一个允许远程调试的选项,开启之后才能在 IDE 中进行相应的操作,此开关默认为关闭状态。如下图所示:

      Maven 插件中进行部署

      Maven 插件的使用场景介于开发人员与运维人员之间,他的设计主要秉承当下比较流行的 DevOps 的理念,可以将部署流程配置化的方式进行发布。即我们可以将部署的配置信息,随代码工程放置在一起,进行版本跟踪,同时也能将应用的配置根据 Spring 中的 Profile 进行区分。按照相应的配置做好之后,只需要执行简单的  mvn toolkit:deploy  即可完成部署。具体可以参见 EDAS 官方文档的《使用toolkit-maven-plugin插件部署应用》。

      CI/CD 中进行部署

      一套标准的 DevOps 流程肯定少不了 CI/CD 的存在,作为市场上使用最广的 CI/CD 工具 Jenkins ,以其简单易用和其丰富的插件能力而著称。EDAS 也补齐了这一平台的插件,这款插件也涵盖了 EDAS 中所有主流场景的部署,尤其在 Kubernetes 这一块,同时集成了镜像构建、推送、部署的能力。具体可以参见 EDAS 官方文档的《在Jenkins中使用edas-jenkins-plugin构建应用到EDAS》。

      同时,目前还有很多的用户在使用云服务云效,云效中集成了强大的流水线的能力,EDAS 是其中的一个内置的流水线的任务模版,名称为部署到EDAS,详情请参考 EDAS 官方文档《使用云效部署Java应用至EDAS》。

      使用 Terraform 进行编排

      Terraform是一种安全有效地构建、更改和版本控制基础设施的工具(基础架构自动化的编排工具)。它编写了描述云资源拓扑的配置文件中的基础结构,例如虚拟机、存储账户和网络接口。Terraform的命令行接口(Command Line Interface,CLI)提供一种简单机制,用于将配置文件部署到阿里云上,并对其进行版本控制。

      EDAS 也集成了当下比较流行的 Infrastructure As Code 的理念,拥抱 Terraform 的生态,提供了一个官方插件,让用户可以以 Infrastructure As Code 的方式将应用编排到对应的底层 IaaS 层资源与其他 PaaS 资源上,文档参见:《使用Terraform部署应用至EDAS》。

      使用 CLI 工具中进行部署

      对于一个资深的运维人员而言,可能最喜欢的操作的方式还是命令行工具。除了使用习惯之外,因为命令行工具同时具备很好的脚本化,和其他的脚本语言进行结合后能具备更丰富的能力。

      EDAS 中的 CLI 工具,目前是依托于阿里云的命令行入口,已 POP API 为命令,API 的参数为命令行的参数进行构建,也就是说其本质还是转换成为一次 POP API 的调用。官方文档请参考:《使用 CLI 快速部署 EDAS 应用》

      结语及后续

      EDAS 的部署工具基本上围绕着开发人员、运维人员、DevOps 场景进行构建,不过对于一次部署而言,触发往往只是提交一个任务,而我们其实更关心任务提交之后的结果,甚至结果对于业务的影响。因为我们每一次任务的触发,其实都是对线上环境的一次变更,变更则很容易产生故障,对业务产生不连续性,根据阿里巴巴的经验,?% 的线上故障都是由于变更产生,所以在 2018 年末,提出了线上变更的三条原则:可灰度、可回滚、可监控。EDAS 也是逐步将这一理念中的各种能力在产品中践行;所以接下来的章节将围绕着线上变更来进行,下一讲将进入第一小节《可灰度》。

      本文作者:孤弋,阿里云高级技术专家,负责 EDAS的开发和用户体验优化工作。

      ]]>
      SpringCloud 应用在 Kubernetes 上的最佳实践--系列内容总结 Fri, 20 Jun 2025 02:20:33 +0800 为了顺应使用 SpringCloud 做为微服务的框架,同时选择 Kubernetes 作为应用与基础设施运维底座这一趋势,EDAS 也紧紧围绕这一典型场景,对它的开发、测试、部署、联调、线上运维等诸多环节中的开发者体验进行深度打磨,发布了全新的 3.0 版本。同时,针对如何在采用了 SpringCloud + Kubenetes 架构的应用上使用 EDAS,我们团队提供各个环节的最佳实践,供开发者参考,本系列将按照一定的周期推出具体的文章讲解,详情如下:

      第一篇:SpringCloud 应用在 Kubernetes 上的云上实践 - 开发篇
      第二篇:SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(开发部署)
      第三篇:SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(工具部署)
      第四篇:SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(可灰度)

      持续更新中·····

      ]]>
      这家在线旅游企业将 Serverless 和微服务进行了完美结合 Fri, 20 Jun 2025 02:20:33 +0800 客户介绍

      百富旅行是全球领先的在线旅游同业交易平台,覆盖机票、火车票、酒店、邮轮、汽车票、门票、旅游、商城等版块,通过整合全球的优势资源,基于云计算和大数据决策为全球旅游从业者提供一站式智能整体解决方案。截至目前,百富旅行已经与600多家航空公司、国内所有火车线路、2500个汽车站,以及60多家邮轮集团进行了业务整合,网络覆盖全球100多个国家和地区。

      业务痛点

      随着业务的飞速发展,百富旅行技术团队通过Spring Cloud 等开源框架搭建了完善的微服务技术架构,并在底层服务器资源方面全面拥抱阿里云ECS,利用云计算提升工作效率,降低工作成本。将微服务应用上云之后,不需要再考虑硬件资源购买以及服务器架设等运维步骤,这样技术团队可以将更多的精力投入到业务需求实现中。随着系统迭代次数的增加,原有的微服务架构开始暴露出一些问题:

      1、微服务运维难度大
      从系统架构的角度,将微服务应用直接部署在云虚拟机上,跟部署在物理机房相比,并没有本质的区别,团队依然需要从底层维护每一个应用实例,包括操作系统调整、磁盘容量规划、JDK等组件安装等工作,这些工作都每一台云虚拟机投入使用的过程中,都是必不可以的。

      2、测试环境利用率低,闲置资源高
      在系统频繁的迭代过程中,不同的开发小组甚至不同的开发人员都需要单独的一套测试环境,久而久之整个技术团队创建了多套测试环境,其中一些测试环境包含了所有的微服务应用,整体资源利用率特别低,造成了大量的资源浪费。而且旅游业务本身也存在非常明显的波峰波谷,微服务架构可以很方便地为每一个应用进行水平扩容,但如果用于扩容的虚拟机资源需要预先购买的话,同样会造成大量的资源闲置。

      选型调研

      近些年在云计算领域异军突起的Serverless架构正好能解决上述这两个问题。由于不需要为Serverlesss应用购买底层服务器资源,直接按需申请,可以免去容量规划、操作系统调优等复杂的运维工作,Serverless架构的弹性伸缩机制也彻底解决了资源的闲置与浪费问题。因此,百富旅行技术团队开始对Serverless架构进行技术预研。

      Serverless架构有两种常见的实现方式:

      第一种是把每个微服务应用进行容器化改造后,统一使用Kubernetes进行编排,并利用云厂商提供的弹性容器实例实现容器层的按需调用。

      这种方式的门槛很高,需要有精通Kubernetes技术的运维小组加入,并且需要团队投入比较大的精力对应用进行容器化改造,暂时不适用于百富旅行这样小规模高效率的技术团队。

      另一种方式是使用类似于AWS Lambda或阿里云FC函数计算引擎,将所有业务逻辑进行函数化重构。

      这种方式基本上需要将之前写的代码推倒重来,而且在一些拥有复杂调用链路的业余环节并不能发挥Serverless的优势,对百富旅行而言更加不合适。

      经过多轮技术调研以及与阿里云技术专家深入交流后,百富旅行技术团队认为,阿里云提供的Serverless应用引擎(SAE)方案最适合他们的Serverless平台。

      区别于其它 Serverless 产品,SAE 直接支持 Spring Cloud、Dubbo 等开发框架,真正实现了 Serverless 架构 + 微服务架构的完美结合。开发者可以通过WAR、JAR、镜像三种方式部署Serverless应用,不需要学习Kubernetes以及容器技术,也能享受Kubernetes和云上弹性容器的技术红利。

      SAE使用实践

      由于SAE属于应用层的Serverless方案,对于之前通过Spring Cloud框架构建的微服务应用,可以非常平滑地逐步迁移到SAE平台上,不涉及任何代码和业务逻辑的修改,这一点也是百富旅行全面拥抱SAE方案最重要的原因。

      image.png
      部署在SAE上的微服务应用,可以按需申请资源,根据实际使用资源量按分钟计费,避免业务不活跃时段的费用支出,有效降低成本,并且微服务应用也能充分的发挥弹性伸缩的价值。特别是对于测试环境,SAE可以做到一键启停,避免了资源闲置问题。对于生产环境的任何一个应用,如果有扩容实例的需求,也能够迅速拉起新的实例,并自动与SLB以及注册中心完成绑定。

      此外,通过SAE提供的应用生命周期管理能力,百富旅行技术团队彻底告别了通过编写脚本进行版本发布的方式。不论是应用的部署、启动、停止、与SLB关联、扩容实例、缩容实例,都可以通过阿里云控制台进行可视化操作。配合SAE的版本管理以及多种灰度发布机制,进一步降低了版本更新过程中的风险。对于新发布的版本可以在生产环境先进行小规模验证,一旦不能满足要求可以一键快速回滚。SAE还额外提供了系统监控功能,能从基础资源、JVM、应用链路等多个层面实时、直观地了解系统运行状态。

      经历了基于SAE的Serverless化演进之后,百富旅行节省了大量云资源的成本投入,并且减少一半以上的运维工作,为业务持续发展打下了坚实的基础。未来,百富旅行将继续基于自身的技术特点不断深入探索Serverless架构,在拥抱新技术的同时也能充分享受到云计算的红利。

      更多解决方案和产品细节,可点击Serverless 应用引擎 SAE >>

      ]]>
      云原生的五大趋势,K8s安卓化位列其一 Fri, 20 Jun 2025 02:20:33 +0800 “未来的软件一定是生长于云上的”,这是云原生理念的最核心假设。而所谓“云原生”,实际上就是在定义一条能够让应用最大程度利用云的能力、发挥云的价值的最佳路径。因此,云原生其实是一套指导软件架构设计的思想。按照这样的思想而设计出来的软件:首先,天然就“生在云上,长在云上”;其次,能够最大化地发挥云的能力,使得我们开发的软件和“云”能够天然地集成在一起,发挥出“云”的最大价值。

      云原生的概念大家并不陌生,很多企业也已经基于云原生的架构和技术理念落地相关实践。那么,这么多企业和开发者热衷和推崇的云原生,未来的发展趋势如何?如何才能顺应云原生的主流方向去发展?怎么在云原生时代“乘风破浪”?

      我们邀请到阿里云原生资深技术专家、CNCF技术监督委员会代表,etcd作者李响和阿里云高级技术专家、CNCF 应用交付领域联席主席张磊分享云原生的理念、发展以及未来趋势,为大家打开新的思路和眼界。

      以下内容共享给大家。

      一、Kubernetes项目的安卓化

      云原生里有一个非常关键的项目,就是Kubernetes。Kubernetes的发展非常迅速,它是整个云原生体系发展的基石。今天我们来观察Kubernetes项目的发展特点,首先,Kubernetes无处不在,无论是在云上,还是用户自建的数据中心里,甚至一些我们想象不到的场景里,都有Kubernetes的存在。

      第二,所有云原生的用户使用Kubernetes的目的,都是为了交付和管理应用。当然这个应用是一个泛化的概念,可以是一个网站,也可以是淘宝这样非常庞大的电商主站,或者是AI作业、计算任务、函数,甚至虚拟机等,这些都是用户可以使用Kubernetes去交付和管理的应用类型。

      第三,今天我们来看Kubernetes所处的位置,实际上是承上启下。Kubernetes对上暴露基础设施能力的格式化数据抽象,比如Service、Ingress、Pod、Deployment,这些都是Kubernetes本身原生API给用户暴露出来的能力。而对下,Kubernetes提供的是基础设施能力接入的标准接口,比如说CNI、CSI、DevicePlugin、CRD,让云能够作为一个能力提供商,以一个标准化的方式把能力接入到Kubernetes的体系中。

      这一点其实跟安卓非常类似,安卓虽然装在你的设备里,但是它能够让你的硬件、手机、电视、汽车等都能接入到一个平台里。对上则暴露统一的一套应用管理接口,让你能够基于安卓系统来编写应用,去访问或者享受到这些基础设施能力,这也是Kubernetes和安卓的相似之处。

      最后,Kubernetes本身并不直接产生商业价值,你不会花钱去购买Kubernetes。这就跟安卓一样,你不会直接掏钱去买一个安卓系统。而类似的,Kubernetes真正产生价值的地方也在于它的上层应用生态。对安卓来说,它今天已经具备了一个庞大的移动端或设备端应用的开发生态,而对于Kubernetes来说也是类似的,只不过现在还在于比较早的阶段。但我们已经能够看到,今天在Kubernetes上构建的商业层很多是垂直解决方案,是面向用户、面向应用这一侧真正能够产生商业价值的东西,而不是Kubernetes本身这一层。这就是为什么我说Kubernetes发展跟安卓很像,当然这可能也是谷歌比较擅长的一个“打法”:全力地去免费推广一个“操作系统”,真正获取商业价值的方式则是是去“收割”操作系统上层的生态价值而不是操作系统本身。

      基于这些现象,我们将Kubernetes的发展趋势概括为以下几点:

      1、云的价值回归到应用本身

      用户使用Kubernetes的本质目的是去交付和管理应用。从这个现象来看,如果Kubernetes 发展下去,那么世界上所有的数据中心和基础设施上面都会有一层Kubernetes,自然而然用户就会开始以Kubernetes为基础去编写和交付以及管理其应用,就跟现在我们会以安卓这样一个操作系统为基础去编写移动应用是类似的。

      这就会导致云上的大多数软件和云产品都是第三方开发的。第三方开发是指所有人都可以面向一个标准界面去开发和交付软件,这个软件本身既可以是自己的软件,也可以是一个云产品。未来,越来越多的第三方开源项目,如MongoDB、Elasticsearch等,都会以云原生理念去开发、部署和运维,最后直接演进成为一种云服务。

      2、云端“豌豆荚”的出现

      有了Kubernetes这样一个标准,开发者面对的就是一个类似于操作系统的界面。由于有更多的应用是面向Kubernetes诞生的,或者说面向Kubernetes去交付的,那么就需要有一个类似于“豌豆荚”的产品,来作为云上的应用商店或者云上的应用分发系统,它的关键能力在于把应用无差别地交付给全世界任何一个Kubernetes上面,就跟用豌豆荚把任何一个安卓应用交付在任何一个安卓设备上的原理是一样的。

      其实今天谷歌已经在做这类产品的尝试了,比如Anthos(面向混合云的应用交付平台),虽然是一款混合云产品,但它本质上是把谷歌云的服务,比如数据库服务、大数据服务,去直接交付于任何一个基于Kubernetes的混合云环境里面去,其实就相当于一款云端的“豌豆荚”。

      3、基于Kubernetes可扩展能力的开放应用平台会取代PaaS成为主流

      由于未来整个应用生态会面向Kubernetes去构建,那么基于Kubernetes可扩展能力的开放应用平台会逐渐取代传统PaaS 而成为主流。基于Kubernetes可扩展能力去构建一个开放的应用平台,其能力是可插拔的,能够去交付和管理的应用类型是多样化的,这才更符合Kubernetes所构建的趋势和生态,所以一些真正高可扩展的平台层项目会大量产生。

      另外,今天我们看到的Kubernetes,跟“理想”中的云原生应用生态之间其实还有很多路要走,这也是阿里云原生团队一直在做的事情,基于Kubernetes在应用层构建更丰富的应用生态,帮助用户实现多样化的需求。

      二、应用与能力的“Operator化”

      纵观云原生时代应用或者云的能力的发展方向,你会发现另一个趋势,就是Operator化。Operator是Kubernetes的一个概念,是指Kubernetes交付的一个实体,这个实体有一个基础模型存在,这个模型分为两部分:一部分是Kubernetes的 API 对象(CRD),另一部分是一个控制器(Controller),如下图所示。
      image.png
      这里要区分两个概念,自定义和自动化。很多人会说 Operator 可以帮助我做自定义,因为很多人都会觉得Kubernetes内置的能力是不够用的,所以用户会利用它的可扩展能力去写一个Controller,从而实现跟多自定义的需求。但自定义只是Operator 中很小的一部分价值,我们今天对应用和能力做Operator化的核心动力在于其实是为了实现自动化,而且只有自动化了,我们才能讲云原生。

      这是因为,云原生带来的最大的红利是可以让我们最大限度、最高效地使用云的能力,二这种最高效、最大化的方式一定没办法通过人工来实现的。换句话说,只有通过自动化的方式去开发、运维应用以及与云进行交互,才能真正把云原生的价值发挥出来。

      而如果要通过自动化的方式跟云进行交互,那么在云原生生态里,必须有一个类似于Controller或者Operator这样的插件的存在。今天阿里巴巴在云上交付的PolarDB、OceanBase等,其实都有一个跟Kubernetes衔接的Controller的存在。通过Controller与基础设施、云进行交互,把云的能力输入到产品里面去。

      在未来,会有大量的云上的应用和对应的运维能力、管理能力都会以Kubernetes Operator 的方式交付。在这个背景下,Kubernetes真正扮演的一个角色就是能力的接入层和标准界面。如下图所示,这是一个非常典型的用户侧Kubernetes集群的样子。
      image.png

      一个用户的Kubernetes只有红框里面这部分是Kubernetes原生提供的API,而大量的能力都是以插件化或者说Operator化的方式存在。就比如上图右边所有这些自定义的资源和能力全部来自于第三方开发,通过Operator 这样一个标准的形态开发出来的能力来服务最终用户的。这就意味着在未来云原生的生态里面,基于CRD Operator的而非Kubernetes原生API的应用和能力会占到绝大多数。

      随着这个趋势的不断演进,越来越多的软件和能力通过Kubernetes Operator去描述和定义,云产品也会开始默认以Kubernetes为底座,基于Operator进行交付。

      正是因为越来越多的Operator的出现,这里就会逐步需要一个中心化的方式去解决Operator潜在的稳定性、可发现性和性能问题,也就是说在未来很可能会有一个横向的Operator管理平台出现,对所有基于Kubernetes Operator开发的应用和能力进行统一管理,从而更好、更专业地服务用户。

      此外,由于未来每一个能力、每一个应用都需要去编写Operator,所以说对开发者友好的Operator编写框架也是未来一个很重要的趋势。这个编写框架可以支持不同语言,如Go、Java、C、Rust语言等,并且编写过程是专注于运维逻辑和应用的管理、能力的管理,而不是专注在Kubernetes的语义和细节上面。

      最后,随着云原生生态的普及,云服务也将实现Operator化,并且面向多集群/混合云场景出现面向应用层的云服务标准化定义与抽象,并在云原生领域逐渐取代IaC项目(比如 Terraform 等)成为云服管理与消费的主流方式。

      三、应用中间件能力进一步下沉

      随着云原生以及整个生态的发展,我们看到应用中间件领域也随之发生了很多改变。从原先最开始的中心化ESB,到后来的胖客户端,逐步演化到今天我们经常提到的Service Mesh这样一种Sidecar化的方式。
      image.png

      其实今天你会发现,无论是云的能力还是基础设施的能力,都在不断丰富,很多原先只能通过中间件做的事情,现在可以很容易通过云服务来实现。应用中间件不再是能力的提供方,而是能力接入的标准界面,并且这个标准界面的构建不再基于胖客户端,而是通过非常普通的HTTP协议、gRPC协议去做,然后通过Sidecar方式把整个服务的接入层跟应用业务逻辑做一个解耦,这其实就是Service Mesh的思想。

      目前Service Mesh只做了传统中间件里面的流量治理、路由策略、访问控制这一层的事情。而实际上,Sidecar 这个模型可以应用到所有中间件的场景里,实现中间件逻辑跟应用业务逻辑完全解耦,让应用中间件能力“下沉”,变成Kubernetes能力的一部分。这样应用本身会更加专一化,更多的关注业务逻辑本身。

      伴随着这个趋势,在Kubernetes这一层还会有另外一个趋势出现,就是Sidecar的自动化的、规模化的运维能力会成为一个必选项。因为Sidecar的数量会极其庞大,应用中间件很可能会演化成Sidecar集群,那么这些Sidecar的管理和规模化的运维能力,会是集群或者云产品的一个必备选项。

      四、下一代DevOps模型与体系

      随着云原生生态的不断发展,云原生理念的不断普及,DevOps的思想很可能也会发生一个本质的变化,即下一代DevOps模型与体系。随着Kubernetes的能力越来越多、越来越强大,基础设施也会变得越来越复杂,那么基于这样一个强大的基础设施去构建一个应用平台就会非常简单,并且这个应用平台最终会取代传统的PaaS平台。

      我们现在之所以在用DevOps这一套思想,实际上是由于基础设施本身不够强大,不够标准化,不够好用,所以我们需要在业务研发侧做一套工具去黏合研发人员和基础设施。例如,基础设施提供的能力是一个虚拟机,怎么能让虚拟机变成研发侧想要的蓝绿发布或者一个渐进式的应用交付系统呢?这就需要一系列的DevOps的工具、CI/CD的流水线来完成。
      但是现在的情况已经发生了变化。基于Kubernetes的基础设施本身的能力已经非常丰富,像蓝绿发布这些能力本身就是Kubernetes可以提供的能力。在这样的背景下,DevOps的发展趋势也会发生很大的改变:

      1、关注点分离

      在 Kubernetes 的背景下,“软件”不再是一个由应用Owner掌控的单一交付物,而是多个Kubernetes对象的集合,而这一堆Kubernetes里面的对象只有很少一部分其实才跟研发有关,所以说有很多对象会不在应用Owner的认知范围内,这就导致平台必须去做关注点分离,研发侧的关注点和运维侧、系统侧的关注点是完全不一样的东西。也就是研发不用再考虑运维方面的细节,比如蓝绿发布怎么做,水平扩容什么策略,只要把业务代码写完交付就好。

      伴随着 Kubernetes和基础设施越来越复杂,概念越来越多,作为平台层是不大可能让研发了解所有的概念,因此未来云原生生态一定会做抽象和分层。每一层的角色只跟属于自己的数据抽象去交互,研发侧有一套自己的声明式API对象,运维侧有一套自己的声明式API对象,每一层的关注点也是不一样的,这会是未来整个DevOps体系里发展的一个重要的背景。

      2、Serverless 泛化

      云原生本身的关注点就是应用,在这样一个背景下,Serverless本身不再是一个独立场景,不再局限在某几个非常垂直的领域,而会变成云原生应用管理体系的一种泛化思想和天然组成部分。我从两个层面解释一下:一是在能力侧,“轻运维”“NoOps”以及“自助式运维能力”会成为应用运维的主流方式。云原生生态上的应用管理会体现出一种轻运维的状态,就是说应用运维不再是一个人工的、非常复杂的过程,而是一组开箱即用的、非常简单的模块化操作。无论是通过Kubernetes还是通过云原生能力,都是对下层基础设施的一个模块化的分装,这跟Serverless所提倡的NoOps理念非常类似。

      二是在应用侧,应用描述会广泛地进行用户侧的抽象,事件驱动和Serverless 理念被拆分和泛化,可以被应用于多样化的场景中而不仅仅是今天狭义的 Serverless 场景比如 FaaS 或者 Container Instance,未来所有的应用都可以实现scale-to-zero。

      3、基于Infrastructure as Data(IaD)思想的应用层技术渐成主流

      第一,基于Infrastructure as Data(IaD)的思想会成为一个主流技术,IaD实际就是Kubernetes的声明式API,声明式API的核心在于把基础设施、应用、能力以一个声明式的文件、声明式的对象去描述,那么这个文件或者对象本身就是“数据”。而Kubernetes或者基础设施这一层是通过数据去驱动的,这就是Infrastructure as Data。这样的思想会延伸出很多技术和前沿的思想,比如GitOps、管道型YAML操作工具(Kustomize/kpt)等。这样的管道型应用管理会成为云原生生态里面一个非常主流的应用管理方式。

      第二,声明式应用定义模型(比如 OAM),以及声明式的CI/CD系统和Pipeline会成为一个新的应用交付的模式。比如传统的Jenkins是一个命令式的组织方式,而随着声明式的Pipeline的出现,加上云原生生态、Kubernetes的普及,基于Infrastructure as Data思想的流水线和下一代的CI/CD系统也会成为业界的主流。这跟以前的CI/CD和流水线有本质的区别,因为这个CI/CD系统里面所有的操作都是一个声明式描述。正因为是声明式描述,所有这些操作以及CI/CD里面的环节都可以托管到Git上,哪怕一个人工审核(Manual Approve)这样的动作都可以托管在Git里面,通过Git去审计和做版本管理等。

      Infrastructure as Data的出现就是告诉我们,未来云原生的系统。一切皆对象,一切皆数据。随着对象和数据越来越多,对他们的管理、审计、验证等就变得越来越复杂,那么围绕它们的策略引擎(Policy Engine)会成为一个非常重要的需求。策略引擎会成为一个非常重要的组件,未来Kubernetes所有的应用平台可能都需要一个策略引擎的存在,帮助用户处理不同场景下对数据的操作策略。

      4、构建于IaD之上的最终用户体验层

      需要注意的一点是,虽然Infrastructure as Data会成为应用层的主流技术,但是它有一个“硬伤”,就是对最终用户并不友好。因为人的大脑比较容易去处理流程化的、规则化的事情,而不是去处理一个静态的数据,所以说在IaD之上会有一层面向最终用户的体验层的存在。这就意味着Kubernetes不会把声明式的数据直接交给最终用户,而是通过其他方式来操作这些数据,比如通过一种能够理解Kubernetes数据模型的动态配置语言(DSL)来完成,或者通过基于API对象的CLI或者dashboard来完成,也可能是通过一种以应用为中心的交互与协作流程来完成。而最终用户体验层会决定产品有没有黏性,这是云原生的这套体系有没有黏性,是不是用户友好的一个关键环节。

      5、DevSecOps

      随着如前所述的下一代DevOps体系的发展,安全会从一开始就变成应用交付的一部分。在业界大家称之为DevSecOps,就是从day zero开始就把安全策略、对安全的考量、安全配置作为应用的一部分,而不是等到应用交付出去了甚至应用已经上线了再去做事后的安全审计和管理。

      五、底层基础设施的Serverless云原生化

      随着云原生体系的发展,云的价值逐渐走向应用层,不断向基于声明式API、基于IaD的理念去发展,那么下层的基础设施也会发生相应的变化。第一个变化是基础设施能力声明式API化、自助化。今天的云是基础设施能力的集大成者,可以认为是一个无限的能力层,今天我们能想象到的基础设施上所有的能力,云都可以提供,这跟以前的基础设施完全不一样。以前云的能力很薄弱,基础设施的能力也很薄弱,所以才需要一个庞大的中间件体系和精密的DevOps体系来做一个“胶水层”,去弥补基础设施跟应用、研发、运维人员之间的鸿沟。

      而未来,应用才是整个云原生生态的主角。应用需要使用某个能力,那么云就会提供这个能力,并且是通过一个标准化的接入层来提供,而不是直接跟基础设施打交道。云原生生态的发展会使得用户侧的视角发生很大的改变,从面向基础设施变为面向应用,从基础设施有什么用户才能用什么,变成用户要什么,基础设施就可以提供什么。以应用为中心的基础设施会是未来基础设施的一个基本形态。
      image.png

      这个理念跟Serverless理念非常类似,我们可以将它称为底层基础设施的Serverless原生化,这意味着基础设施会在未来也逐渐的声明式API化,而声明式API化带来的一个直接结果就是他会变成一个自助化的基础设施。

      另外,由于基础设施能够实现声明式API化,实现自助化,那么打造更加智能化的基础设施就成为一个重要方向。因为基础设施系统的模块化能力变成了一个数据化的定义方式,那么就可以和容易的通过监控数据、历史数据来驱动基础设施的运转,也就是“自动驾驶的基础设施”。数据驱动的智能化基础设施会在未来成为可能,当然其前提是基础设施本身实现声明式API化和自助化。

      与此同时,由于应用层本身会Serverless泛化,像“scale to 0 ”和“pay as you go”这些功能,会成为应用的一个基础的假设,导致资源层也会走向极致弹性+无限资源池的方向。作为一个智能化的基础设施,可以去做更加智能的调度与混部,从而提供极致的资源利用效能,实现成本的极低化。

      与此同时,由于要实现极致的资源效能,就意味着底层一定是一个强多租架构,并且这个强多租架构是面向Kubernetes 的,跟Kubernetes有一个天然的、非常融合的集成。这体现在两个方面:第一,在运行时这一层,这个基础设施会倾向走基于硬件虚拟化的容器运行时而非传统虚拟机的方向,比如Kata Container,并且认为神龙裸金属服务器更适合做宿主机。伴随着这套技术的发展,轻量化的VMM(虚拟化管理技术)会成为优化容器运行时、优化整个基础设施敏捷度的一个关键技术和关键链路。

      第二,强多租的控制面会针对不同租户做物理隔离,而不只是逻辑隔离,这是Kubernetes数据模型的要求,即租户的控制面板之间需要有强的物理隔离,这就是为什么我们讲未来的强多租架构一定会面向Kubernetes来构建。阿里内部也是看到了这样的趋势,在不断做一些尝试,去更好地响应未来Serverless原生化的基础设施的发展趋势。

      云计算的下一站,就是云原生;IT架构的下一站,就是云原生架构,这是属于所有开发者最好的时代。阿里云将于7月上线《云原生架构白皮书》,助力所有的开发者、架构师和技术决策者们,共同定义云原生,拥抱云原生。

      ]]>
      云原生时代业务架构的变革:从单体迈向Serverless Fri, 20 Jun 2025 02:20:33 +0800 如今,各行各业都在谈数字化转型,尤其是新零售、传媒、交通等行业。数字化的商业形态已经成为主流,逐渐替代了传统的商业形态。在另外一些行业里(如工业制造),虽然企业的商业形态并非以数字化的形式表现,但是在数字孪生理念下,充分利用数据科技进行生产运营优化也正在成为研究热点和行业共识。

      企业进行数字化转型,从生产资料、生产关系、战略规划、增长曲线四个层面来看:

      • 生产资料:数据成为最重要的生产资料,需求/风险随时变化,企业面临巨大的不确定性。
      • 生产关系:数据为中心,非基于流程和规则的固定生产关系。网络效应令生产关系跨越时空限制,多连接方式催生新的业务和物种。
      • 战略规划:基于数据决策,快速应对不确定的商业环境。
      • 增长曲线:数字化技术带来触达海量用户的能力,可带来突破性的增长。

      从云服务商的角度来看云的演进趋势,在Cloud 1.0时代,基础设施的云化是其主题,采用云托管模式,云上云下的应用保持兼容,传统的应用可以直接迁移到云上,这种方式的核心价值在于资源的弹性和成本的低廉;在基础设施提供了海量算力之后,怎么帮助用户更好地利用算力,加速企业创新的速度,就成为云的核心能力。

      如果仍在服务器上构建基础应用,那么研发成本就会很高,管理难度也很大,因此有了Cloud 2.0,也就是云原生时代。在云原生时代,云服务商提供了丰富的托管服务,助力企业数字化转型和创新,用户可以像搭积木一样基于各种云服务来构建应用,大大降低了研发成本。

      云原生应用要素

      云原生应用有三个非常关键的要素:微服务架构,应用容器化和Serverless化,敏捷的软件交付流程。

      1. 微服务架构

      单体架构和微服务架构各有各的特点,其主要特点对比如下图所示。总的来说,单体架构上手快,但是维护难,微服务架构部署较难,但是独立性和敏捷性更好,更适合云原生应用。image.png

      2. 应用容器化和Serverless化

      容器是当前最流行的代码封装方式,借助K8s及其生态的能力,大大降低了整个基础设施的管理难度,而且容器在程序的支撑性方面提供非常出色的灵活性和可移植性,越来越多的用户开始使用容器来封装整个应用。

      Serverless计算是另外一种形态,做了大量的端到端整合和云服务的集成,大大提高了研发效率,但是对传统应用的兼容性没有容器那么灵活,但是也带来了很大的整洁性,用户只需要专注于业务逻辑的编码,聚焦于业务逻辑的创新即可。

      3. 敏捷的应用交付流程

      敏捷的应用交付流程是非常重要的一个要素,主要包括流程自动化,专注于功能开发,快速发现问题,快速发布上线。

      Serverless 计算

      1. 阿里云函数计算

      Serverless是一个新的概念,但是其内涵早就已经存在。阿里云或者AWS的第一个云服务都是对象存储,对象储存实际上就是一个存储领域的Serverless服务;另外,Serverless指的是一个产品体系,而不是单个产品。当前业界云服务商推出的新功能或者新产品绝大多数都是Serverless形态的。阿里云Serverless产品体系包括计算、存储、API、分析和中间件等,目前云的产品体系正在Serverless化。

      阿里云Serverless计算平台函数计算,有4个特点:

      • 和云端无缝集成:通过事件驱动的方式将云端的各种服务与函数计算无缝集成,用户只需要关注函数的开发,事件的触发等均由服务商来完成。
      • 实时弹性伸缩:由系统自动完成函数计算的弹性伸缩,且速度非常快,用户可以将这种能力用在在线应用上。
      • 次秒级计量:次秒级的计量方式提供了一种完全的按需计量方式,资源利用率能达到百分之百。
      • 高可用:函数计算平台做了大量工作帮助用户构建高可用的应用。

      那么,阿里云函数计算是如何做到以上4点呢?阿里云函数计算的产品能力大图如下图所示,首先函数计算产品是建立在阿里巴巴的基础设施服务之上的产品,对在其之上的计算层进行了大量优化。接着在应用层开发了大量能力和工具,基于以上产品能力,为用户提供多种场景下完整的解决方案,才有了整个优秀的函数计算产品。函数计算是阿里云的一个非常基础的云产品,阿里云的许多产品和功能均是建立在函数计算的基础上。目前阿里云函数计算已经在全球19个区域提供服务。image.png

      2. Serverless帮助用户简化云原生应用高可用设计、实施的复杂度

      云原生应用的高可用是一个系统的工程,包括众多方面,完整的高可用体系构建需要很多时间和精力。那么Serverless计算是如何帮助用户简化云原生应用高可用设计、实施的复杂度呢?

      如下图所示,高可用体系建设要考虑的点包括基础设施层、运行时层、数据层以及应用层,且每一层都有大量的工作要做才可以实现高可用。函数计算主要是从容错、弹性、流控、监控四方面做了大量工作来实现高可用,下图中蓝色虚线框所对应的功能均由平台来实现,用户是不需要考虑的。蓝色实线框虽然平台做了一些工作来简化用户的工作难度,但是仍需要用户来进行关注,而橘红色的实线框代表需要用户去负责的部分功能。结合平台提供的功能和用户的部分精力投入,可以极大地减轻用户进行高可用体系建设的难度。image.png
      函数计算在很多方面做了优化来帮助用户建设高可用体系。下图展示了函数计算在可用区容灾方面的能力。从图中可知,函数计算做了相应的负载均衡,使得容灾能力大大提升。

      image.png

      函数计算多可用区容灾


      下图展示的是函数计算对事件的异步处理,其处理流水线主要包括事件队列、事件分发、事件消费三个环节,在每一个环节上都可以进行水平伸缩,其中一个比较关键的点是事件的分发需要匹配下游的消费能力。另外,通过为不同函数指定不同数量的计算资源,用户能方便地动态调整不同类型事件的消费速度。此外,还可以自定义错误重试逻辑,并且有背压反馈和流控,不会在短时间内产生大量请求时压垮下一个服务。

      image.png

      函数计算事件异步处理


      在函数计算的可观测性上面,提供了日志收集和查询功能,除了默认的简单日志查询功能外,还提供了高级日志查询,用户可以更方便地进行日志分析。在指标收集和可视化方面,函数计算提供了丰富的指标收集能力,并且提供了标准指标、概览信息等视图,可以更方便用户进行运维工作。

      下图是应用交付的一个示意图,在整个应用的交付过程中,只有每个环节都做好,才能够建设一个敏捷的应用交付流程,其核心是自动化,只有做到了自动化,才能提升整个流水线的效率和敏捷度。image.png

      敏捷的应用交付流程


      下图展示了自动化应用交付流水线在每个环节的具体任务。其中需要注意的是做到基础设施即代码,才能进行模板定义和自动化设置应用运行环境,进而实现自动化的持续集成等。

      image.png

      自动化应用交付流水线

      做到了应用的自动化交付之后,对整个研发效率的帮助是非常大的。在Serverless应用上,阿里云提供了多种工具来帮助用户实现基础设施即代码。Serverless的模型有一个很好的能力,就是同一份模板可以传入不同的参数,进而生成不同环境的定义,然后通过自动化地管理这些环境。

      对于应用本身不同服务版本的交付和灰度发布,函数计算提供了服务版本和服务别名来提供相应的服务,整个应用的灰度发布流程可以简化成一些API的操作,大大提升业务的效率。通过Serverless计算平台提供的这些能力,整个软件应用的交付流水线自动化程度得到了大幅度的提高。

      函数计算还有一个很有用的功能——对存量应用的兼容性。通过Custom runtime,能够适配很多的流行框架,兼容传统应用,使其能够很容易地适配到Serverless平台上面,由控制台提供应用的创建、部署、关联资源管理、监控等一系列服务。

      除了函数计算,还可以用Serverless工作流对不同的应用环节、不同的函数进行编排,通过描述性的语言去定义工作流,由其可靠地执行每一个步骤,这就大幅度降低用户对于复杂任务的编排难度。

      应用场景案例

      函数计算有几个典型的应用场景,一个就是Web/API后端服务,阿里云已经有包括石墨文档、微博、世纪华联在内的多个成功应用案例。

      函数计算的另外一个应用场景就是大规模的数据并行处理,比如往OSS上面上传大量的图片、音频、文本等数据,可以触发函数做自定义的处理,比如转码、截帧等。这方面的成功案例包括虎扑、分众传媒、百家互联等。

      函数计算还有一个应用场景就是数据实时流式处理,比如不同的设备产生的消息、日志发送到消息队列等管道类似的服务中,就可以触发函数来进行流式处理。

      最后一个应用场景就是运维的自动化,通过定时触发、云监控事件触发、流程编排等方式调用函数完成运维任务,大大降低运维成本和难度,典型的成功案例有图森未来等。

      图森未来是一家专注于L4级别无人驾驶卡车技术研发与应用的人工智能企业,面向全球提供可大规模商业化运营的无人驾驶卡车技术,为全球物流运输行业赋能。在路测过程中会有大量数据产生,而对这些数据的处理流程复杂多变,即使对于同一批数据,不同的业务小组也会有不同的使用及处理方式。如何有效管理不同的数据处理流程、降低人为介入频率能够大幅的提高生产效率。

      路测不定时运行的特点使得流程编排任务运行时间点、运行时长具有极大的不确定性,本地机房独自建立流程管理系统难以最大优化机器利用率,造成资源浪费。而图森未来本地已有许多单元化业务处理脚本及应用程序,但因为各种限制而无法全量的迁移上云,这也对如何合理化使用云上服务带来了挑战。

      针对上述情况,图森未来开始探索数据处理平台的自动化。阿里云 Serverless 工作流按执行调度的次数计费,具有易用易集成、运维简单等诸多优点,能够很好的解决上述场景中所遇到的问题,非常适合这类不定时运行的离线任务场景。

      Serverless 工作流还支持编排本地或自建机房的任务,图森未来通过使用Serverless 工作流原生支持的消息服务MNS解决了云上云下的数据打通问题,使得本地的原有任务得到很好的编排及管理。

      除了调度外,Serverless 工作流也支持对任务的状态及执行过程中所产生的数据进行维护。图森未来通过使用任务的输入输出映射及状态汇报机制,高效的管理了流程中各任务的生命周期及相互间的数据传递。

      在未来,随着业务规模的扩大,图森未来将持续优化离线大数据处理流程的运行效率及自动化水平。通过各种探索,图森未来将进一步提升工程团队的效率,将更多的精力和资金投入到业务创新中去。

      总结

      Serverless 工作流是阿里云 Serverless 产品体系中的关键一环。通过 Serverless 工作流,用户能够将函数计算、视觉智能平台等多个阿里云服务,或者自建的服务,以简单直观的方式编排为工作流,迅速构建弹性高可用的云原生应用。

      自2017年推出函数计算起, 该服务根据应用负载变化实时智能地弹性扩缩容,1分钟完成上万实例的伸缩并保证稳定的延时。目前已经支撑微博、芒果TV、华大基因、图森未来、石墨科技等用户的关键应用,轻松应对业务洪峰。

      ]]>
      微服务引擎 MSE 2.0 重磅发布 Fri, 20 Jun 2025 02:20:33 +0800 直播主题:微服务引擎 MSE 2.0 重磅发布

      【直播时间】7月8日(今天) 15:00--16:30
      【直播嘉宾】子墚 ,阿里云产品专家;岛风,阿里云高级开发工程师;亦盏,阿里云技术专家
      【直播简介】微服务引擎MSE在原注册中心托管的基础上,新增配置中心托管和微服务治理功能,并通过先进的 Java Agent 技术使得您的应用无需修改任何代码和配置,兼容 Spring Cloud / Duboo 近 5 年的所有版本,客户可享有业内首个集服务注册、服务配置和服务治理于一体的非托管型 PaaS 产品。目前,配置中心不收费,治理中心公测期免费开放。

      直播间地址:戳这里~

      目前微服务引擎 MSE 新购用户享九折优惠,快上车,戳这里,查看详情~

      海报-开发者直播间.png

      ]]>
      节约服务器成本50%以上,独角兽完美日记电商系统容器化改造历程 Fri, 20 Jun 2025 02:20:33 +0800 完美日记创立于2017年,这家公司上线不到两年即成为天猫彩妆销冠,2019年成为11年来第一个登上天猫双十一彩妆榜首的国货品牌,包揽天猫2019全年彩妆销冠;2020年4月成为首个亮相天猫超级品牌日的国货彩妆品牌,同时勇破彩妆品牌销售纪录。另外,完美日记已在全国各地开设了100家线下店,计划至2022年底开店超600家。截至2020年4月,品牌SKU超过700个,全网用户粉丝数量超过2500万,月曝光量10亿+。

      “轻研发、重营销”是流量思维企业的通病,为了“打造互联网时代新的美妆集团”,在依靠流量和营销快速占据市场的同时,完美日记也在不断夯实其技术底座。今年4月,完美日记已完成IT系统全面容器化,保证了每一次大促活动的系统稳定性和可用性,同时利用阿里云ACK容器快速弹性扩缩容,节约服务器成本50%以上。

      1、完美日记容器化改造之路

      对于一家创业公司而言,常常有三个问题摆在面前:

      如何高效、低成本地搭建系统,同时确保安全稳定?
      如何敏捷构建和发布应用,满足业务需求?
      如何提高团队开发效率,确保开发质量?

      早期大部分互联网公司都是直接购买服务器,租用IDC机房的机架部署,应用是直接运行在物理机上,如果要扩展必须购买新的服务器。IDC会频繁出现各种故障,如果遇到IDC迁移就更麻烦,必须半夜搬机器,天亮前上线,对于企业来说,在成本、服务稳定性、工作效率上都是很大的消耗。

      2019年双11前期,完美日记小程序刚刚上线两个月,就经历双11大促的磨砺。在这两个月里,传统的部署方式,特别是有部分应用需要(openrestry)在SLB上面配置,那么运维人员就要在SLB上一个个勾选服务器,这会导致发布版本的时间需要半个小时以上。如果发版过程中出现问题,往往时间还会延长到一个小时以上。

      在扩容机器的时候,使用其中2台服务器在阿里云打OS镜像,采用开机自启动脚本方式启动应用,针对每次运营活动的实际情况进行扩容。为了保持系统的稳定性,运维人员就需要在每晚23:00点以后通过人工操作进行扩容,手工配置SLB。最后测试人员进行测试,平均每次扩容都需要半个小时以上。并且由于双11期间处于大流量、高并发的场景,整个运维人员对服务器维护、版本迭代、数据库运维等都必须格外谨慎,稍有不慎会导致线上生产事故,服务器运维压力巨大。

      2019年双11之后,完美日记就开始针对性测试阿里云容器服务ACK,并开始容器化改造。

      之所以选择容器技术,是因为完美日记要构建一套现代化IT系统以满足快速变化的需求和挖掘更多的数据价值。具体来看,一方面,完美日记对业务的快速创新以及现有业务的实时性和交互性需求都在不断地增长;另外一方面,完美日记对数据的重视程度也在不断提高,尤其是用户数据的重要性。如何提供优于竞争对手的服务和用户体验,如何合理、有效地发掘更多的数据价值,成为完美日记迫切的需求。容器技术以其独有的高效敏捷和易于扩展的特性,加之庞大的生态系统,可以充分满足完美日记不同阶段的IT需求,这也是完美日记最终选择IT系统全面容器化改造的原因。

      完美日记最开始是自建K8s,使用的是K8s开源版本,但是开源版本有很多bug未知,安全性也是未知,并没有一个比较友好的Web操作界面,还需要大量运维人员解决运行时出现突然的各种问题。从成本和效率等维度来看,并不是一条便捷的路,思虑再三,完美日记最终选择阿里云容器服务ACK。“我们的技术人员跟阿里云的技术人员其实非常熟悉,在双11期间他们也给予了很多技术层面的支持,我们遇到的问题他们基本都遇到过,我们没遇到的问题,他们也都遇到过,站在巨人的肩膀上进行容器化改造,对于当下的完美日记而言,是最合适的。”

      完美日记的容器化实践是按照项目区分两条线并行,第一条线是一次性前后端全部迁移,第二条线是分应用分批次前后端分别迁移。

      (1)一次性前后端全部迁移

      2019年11月初-2019年11月中旬,完美日记开始计划容器化改造的准备事宜以及改造方案,包括容器化改造方案初步实施,阿里云K8s选型,阿里云K8s选型后进行初步测试,结合公司情况和人员相配比情况,最终选择了阿里云托管K8s Master版本进行大规模测试工作,并开始准备UAT环境切换前期工作等事宜。

      2019年11月中旬,第一次切换UAT环境到K8s中失败,因为还有部分在开发中的模块,而K8s中没有对应的模块,因此切换回非K8s环境。

      2019年11月底-2019年12月初,将UAT环境切换到K8s中,这次切换吸取了第一次切换失败的经验,UAT环境正式切换到K8s中。

      2019年12月初-2019年12月中,观察整个UAT环境是否存在有重大问题,然后进行调整。将整个K8s UAT环境按照双11量级进行四轮压力测试,将结果反馈,然后不断进行调整。2019年12月中,尝试将后台正式环境切换到K8s正式环境中,但由于UAT环境中代码版本和正式环境中代码版本不一致,导致第一次尝试切换失败。

      2019年12月中,在第一次切换后台失败中吸取了版本不一致的教训后,经过一天的努力终于将后台正式环境切换到K8s正式环境中,正式环境走出艰难的容器化改造第一步。2020年1月初,经过一天努力,将正式环境顺利切换到K8s正式环境中。

      (2)分应用分批次迁移

      2019年11月底开始准备迁移测试环境方案,2019年12月初,后端和中间件开始新增UAT环境。

      2020年1月2日,后端准备完成。1月3日准备开始前端,1月17日前端完成、UAT环境正式使用。1月17日开始准备正式环境迁移方案,2月迁移方案完成,2月中上旬开始迁移后端,3月中旬后端迁移完成,ZooKeeper、Eureka迁移完成。3月下旬,前端开始迁移,4月初前端基本迁移完成。最终在4月中旬,完美日记IT系统全部迁移完成。

      至此,完美日记全面容器化改造完成。image.png
      在容器化部署过程中,利用ACK的快速弹性应对大促时的资源快速扩容。将完美日记IT系统提前接入阿里云链路追踪产品ARMS,用于对分布式环境下复杂的服务调用进行跟踪,对异常服务进行定位,完美日记可以在测试和生产中快速发现问题,快速修复。使用性能测试服务PTS进行压测,利用PTS的秒级流量拉起、真实地理位置流量等特性,以最真实的互联网流量进行压测。收集压测数据,分析系统强弱依赖和关键瓶颈点,对关键业务接口、关键第三方调用、数据库慢调用、系统整体负载等进行限流保护。在大促前进行ECS/RDS/安全等产品扩容、链路梳理、缓存/连接池预热、监控大屏制作、后端资源保障等,帮助完美日记在大促平稳进行,保持丝般顺滑。

      除了采用容器服务ACK之外,完美日记在一开始进行容器化改造时就使用了阿里云镜像企业版ACR EE,它的优势是比自建harbor要稳定与低成本,因为自建harbor需要考虑计算、数据库以及磁盘成本,如果项目很多或者镜像比较多,那么磁盘成本将比较高。镜像企业版不用考虑维护成本。另外,镜像企业版并发比自建harbor要高,如果大批量进行扩容,自建harbor往往容易出镜像PULL问题,但是镜像企业版就没有这种担忧。

      另外,完美日记也通过ARMS Prometheus来监控系统可能出现的问题,并能针对性地解决问题。ARMS还可以解决整个K8s底层监控(Prometheus)的维护和成本高的难题,它能监控应用每个pod资源使用情况,对pod资源进行调整。K8s底层监控(Prometheus)可以做一个自定义大盘,将Prometheus全部监控信息完整显示出来。

      容器化改造之后,整个系统“轻松了很多”。1月初,在切换到K8s正式环境后,扩容时间只需要90秒左右,节约了6~8倍时间,减少了一名服务器运维人员。根据运营节奏进行扩容,服务器扩容成本节约70%~90%。同时,部署效率大幅提升,可根据文件模板秒级创建一个服务,部署时间减少90%以上。

      另外,服务器资源自动计算部署到服务器,利用隔离技术可部署多个项目服务器,利用率提高50%以上。服务模块的自动负载均衡无需人工干预,工作量减少90%以上。服务模块伸缩容无需编写脚本,只需点击伸缩按钮即可,减少人工错误率,工作量减少70%以上。服务模块不可用会自动剔除,自动重启服务模块。服务器宕机时,服务器上运行的服务模块会自动转移到可用服务器上,无需人工干预,工作量减少100%。

      2、容器化改造更大的挑战是在技术和人员上做好准备

      当企业完成了容器化改造之后,在生产环境中应用容器技术,并计划扩大应用规模,这时企业就必须在技术和人员上做好准备:运维人员是否有足够的能力来应对大规模应用带来的挑战,研发人员是否有足够的技术准备能随时解决大规模应用带来的问题,产品的架构设计是否可以满足未来的企业需求,同时组织架构和文化是否已经适应企业新的战略发展等。

      换句话说,如何让项目组和开发人员之间达成技术同频、战略同频更具挑战性,这其实也是很多在做容器化改造的企业面临的共同难题。

      出现这个问题的核心是项目组的开发人员、架构师、运维人员关注点不一致。开发人员关注系统平稳运行和业务开发,而不关心生产环境底层,只要不影响到生产环境和测试环境就可以。架构师关注底层是否稳定运行,技术架构是否符合未来3~5年技术发展,技术是否简单高效等。运维人员关注发布版本是否简单高效,环境是否能统一,扩缩容时间成本,底层运维过程是否能有解决方案等。

      正是由于三方的关注点不同,因此在迁移过程中就不可避免会花费大量的沟通成本。因为K8s这套系统有别于传统的部署过程,开发人员对 centOS系统、Nginx、MQ、MySQL、查询日志等比较熟悉,但对于K8s不甚了解,Ingress、Docker配置化、Deployment配置、Service等往往已经到了开发人员对技术认知的边界了,这就需要花费较长的时间去解答大家的疑问,才能往下一步进行。

      对于这类问题,每个企业的解决方案都不同,最核心的就是把相关人员的知识边界尽量拉到同一级别,最大程度地减少沟通成本和冲突。完美日记是采用“及时同步、责任到人、内部培训”的方法,比如每次在任何环境做的调整都需要在容器化改造群内通知相关人员,保证大家的认知一致;在内部推进“谁负责谁完善”的文档制度;同时组织一些内部技术培训,让关键开发人员在公司内部对K8s进行培训讲解。还有就是推进企业内部新的、统一的技术文化等。

      3、未来规划

      目前各大公有云厂商都推出了容器服务,还有不少独立的容器云公司。如果企业一开始就是建立在公有云之上,推荐直接使用相应的容器服务,不仅可以快速搭建系统,还能大幅降低运维成本,提高效率,轻松实践DevOps。在容器环境下,很多日常操作都自动化或半自动化了,比如应用的部署和发布、扩容等,容器编排具有自愈能力,即使出现问题,也能减少人工的干预,大大减轻运维人员的工作压力。

      完美日记下一步会重点关注三方面,一是进行Ingress+Gateway单独部署;二是使用ECI+HAP+EW+AHAS(自动扩容数据来源)进一步优化成本,应对突发流量;三是考虑采用服务化网格技术。

      如今,云原生已经成为企业数字化转型的关键策略,由于应用需要快速开发和交付,这就促使企业采用云原生的方法来开发应用,以提高效率,并增加灵活性。对于身处云原生时代的企业和开发者而言,不仅需要了解如何通过容器实现构建应用的新方式,更是要以开阔的视野和开放的心态去拥抱云原生生态。

      对于企业而言,需要具备一定的前瞻性,对于容器生态圈的主流技术和发展要有足够的把握,才能更好地将现有业务与容器技术相结合。随着企业对技术的不断探索,业务系统的逐步演进,应用规模的日渐增大,如何更好地与开源生态系统相结合,扩大企业的技术影响力,同时引入更合适的人才,是云原生时代下企业要考虑的问题。

      点击阅读原文,了解更多阿里云容器服务ACK技术详解与客户案例。
      阅读原文链接:https://www.aliyun.com/product/kubernetes?spm=5176.12825654.1kquk9v2l.1.e2cd2c4aqP4QCp

      ]]>
      SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(可灰度) Fri, 20 Jun 2025 02:20:33 +0800 前言

      前三篇我们介绍了应用的开发和部署,那么在应用成功上云后,我就要面对应用的管理话题了,这一篇我们来看看如何做线上发布,并且是可灰度的。

      第一篇:《SpringCloud 应用在 Kubernetes 上的最佳实践 — 开发篇》
      第二篇:《SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(开发部署)》
      第三篇:《SpringCloud 应用在 Kubernetes 上的最佳实践 — 部署篇(工具部署)》

      在新版本上线时,无论是从产品稳定性还是用户对新版本的接收程度上考虑,直接将老应用升级到新版本应用都有很大风险的。我们一般的做法是,保证新老版本同时在线,并且先将少部分流量切换到新版本应用上,同时在此期间对新版本的应用请求进行观察。在确认新版本没有问题后,再逐步将更大比例的流量切换到新版本上。这个过程的核心是可以对流量的流入转发规则进行配置,EDAS的金丝雀发布能力,提供了多个版本同时在线的能力,并且能提供了灵活的配置规则来给不同的版本进行流量分配。

      部署在EDAS Kubernetes 集群中的 Spring Cloud 微服务应用,在新版本发布的时候可以使用金丝雀发布进行小规模验证,验证通过后再全量升级。

      金丝雀发布配置

      首先,进入EDAS的应用部署页面,对我们要进行部署升级的应用进行发布,在这里我们选择金丝雀(灰度)发布。需要注意的是,对灰度发布的流量控制,当前只对非入口应用的Dubbo和Spring Cloud应用生效。所谓入口应用,即承接外部流量的第一个应用节点。并且若您的应用使用了HPA、Rancher、Istio、或者依赖Deployment.Metadata.Name或 Deployment.Metadata.Uid的功能与配置等K8s原生功能或配置时,请勿使用灰度发布或分批发布。否则,应用部署之后,这些K8s原生功能或配置将出现异常。
      1.png

      在发布页面,可以选择通过上传JAR包或者填入JAR包地址的方式选择要进行发布的新版本应用部署包。
      2.png

      在选择好要进行发布的新版本应用部署包后,接下来进行发布策略的配置。这里分为两个部分,第一部分可以对发布批次进行设置,例如设置发布灰度批次,首批进行灰度的pod实例个数,分批间处理方式等。第二部分可以对流量灰度规则进行配置,我们可以选择按流量内容进行灰度或者简单地按照流量比例进行灰度。下面详细介绍这两种发布策略配置。

      设置发布策略

      在批次发布这里我们可以进行的配置有:

      • 首批灰度数量:在点击发布后,会首先将首批灰度数量个数的实例进行新版本的发布。为了保证应用的稳定性,首批灰度的实例数不能超过应用实例总数的50%。比如当前实例数是7台,那么最多只能选择3台作为首批灰度的实例;
      • 剩余批次:首批灰度发布完成后,剩余的应用实例将按照此处指定的批次发布完成;
      • 分批间处理方式:剩余批次间的处理方式可选择手动或者自动,若选择自动,则剩余的几个批次将在前一批发布完成后进行自动发布,自动发布的批次间隔也可进行配置,例如配置每批次在发布完成后,30分钟后自动进行下一批次的发布;
      • 批次内部署间隔:每一批次内,如果此批次内要发布的应用实例数大于 1,则要进行此配置指定批次内实例部署间隔。


      在下面的例子中,我们现在有7个pod应用实例,选择首批对2个实例进行灰度升级。在首批2个实例的灰度发布完成后,将剩下的5个实例分3个批次进行发布。这3个批次的批次间处理方式选择自动发布,在当前批次发布完成后30分钟后自动进行下一批次的发布。同时,由于第2批次和第2批次内实例个数为两台,因此选择批次内两台实例部署间隔为60秒。在发布页面右侧可以对我们的发布策略配置信息进行预览。

      3.png

      设置灰度规则

      目前支持按内容灰度按比例灰度两种方式设置灰度规则。按请求内容进行灰度支持将请求内容符合指定灰度规则条件的流量作为灰度流量,进入到灰度实例中,例如,选择用户ID模100小于等于40的流量作为灰度流量进入灰度实例进行处理,而用户ID模100大于40的仍然进入非灰度实例进行处理,如图1所示。而按流量比例进行灰度是指,将制定比例的请求流量作为灰度流量进入灰度实例进行处理,例如指定40%的流量作为灰度流量,如图2所示。

      4.png5.png

      按请求内容进行灰度

      按请求内容进行灰度可以进行下面指定参数的配置,来决定有哪些请求内容特征的流量将作为灰度流量进入灰度实例中。

      • 协议类型:可选择Spring Cloud和Dubbo,这里我们主要介绍Spring Cloud协议。在Spring Cloud协议下需要对HTTP请求路径进行配置;
      • 条件模式:针对下面配置的的条件列表,可配置条件模式为:同时满足下列条件满足下列任一条件。符合条件模式的请求将作为灰度流量;
      • 条件列表:Spring Cloud协议下可分别对 Cookie、Header 和 Parameter 3 种请求内容进行条件配置;

      6.png

      按比例进行灰度

      按比例灰度即设置流量比例,然后请求流量会按配置的比例被转发到当前的灰度分组中进行处理。7.png

      灰度发布并验证新版本应用是否符合预期

      配置好发布配置后,即可开始进行灰度发布,EDAS 将先在指定的灰度分组中部署新版本应用,可在进入变更详情页面查看部署进度和状态。如果在灰度发布时,发现新版本有问题,还可以终止变更并对应用进行回滚。
      在灰度的发布过程中,可对应用进行监控,以监控灰度流量是否符合预期,同时可以对应用状态进行新老版本的对比。在当前批次的灰度流量验证完成后,在变更详情页面单击开始下一批,完成后续分批发布。如果在验证过程中,发现新版本应用有问题,可以在变更详情页面右上角单击立即回滚。在弹出的立即回滚对话框确认回滚的影响,然后单击回滚。
      关于如何监控灰度流量,可以参考EDAS文档《监控灰度流量》
      8.png

      灰度发布后,在基本信息页面查看部署包是否为新部署的应用版本。在实例部署信息页面查看应用实例的运行状态是否为运行正常。

      9.png

      结语及其后续

      本章我们介绍了如何对EDAS Kubernetes集群上的Spring Cloud应用进行灰度发布,在灰度发布过程中,我们可以灵活地配置发布策略、灰度规则以及在发布过程中对流量及应用状态进行监控,并且提供了终止回滚等操作,最大程度地保证应用能够平滑地进行版本升级。接下来的文章中,我们将详细介绍在发布过程中如何对应用进行监控。

      ]]>
      【升级】7月事件总线EventBridge升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【事件总线EventBridge】【升级通知】

      升级窗口:

      北京时间2020年7月7日 00:00 - 06:00

      北京时间2020年7月9日 00:00 - 06:00

      北京时间2020年7月14日 00:00 - 06:00

      北京时间2020年7月16日 00:00 - 06:00

      北京时间2020年7月21日 00:00 - 06:00

      北京时间2020年7月23日 00:00 - 06:00

      北京时间2020年7月28日 00:00 - 06:00

      北京时间2020年7月30日 00:00 - 06:00

      升级内容:华东1(杭州)地域的事件总线服务升级。

      升级影响:升级期间事件总线EventBridge相关服务及控制台访问可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。如需在控制台进行管理操作,请避开维护时间段。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】7月消息队列MQ升级计划通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【消息队列MQ】【升级通知】

      升级窗口:

      北京时间2020年07月02日 01:00 - 09:00

      北京时间2020年07月07日 01:00 - 09:00

      北京时间2020年07月09日 01:00 - 09:00

      北京时间2020年07月14日 01:00 - 09:00

      北京时间2020年07月16日 01:00 - 09:00

      北京时间2020年07月21日 01:00 - 09:00

      北京时间2020年07月23日 01:00 - 09:00

      北京时间2020年07月28日 01:00 - 09:00

      北京时间2020年07月31日 01:00 - 09:00
      升级内容:所有地域的MQ服务(包含Tcp、Mqtt、Http接入方式)。

      升级影响:升级期间MQ控制台和集群中每个服务节点可能出现秒级闪断(闪断时间和集群规模正相关),客户端会自动重试机制,一般不会影响业务,但可能会有异常日志。
      升级期间,消息可能会有重复,应用应该按最值实践,做好消息的幂等;同时可能会有消息延迟的现象。
      如需在控制台进行管理操作,请避开维护时间段。HTTP接入可能会出现闪断或者拒绝连接现象,每次闪断或拒绝连接不会超过1分钟,请在客户端中做好重连重试机制。同时,您可使用监控管理功能对重要业务进行监控,具体设置方法请点击MQ控制台左侧监控报警菜单。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】7月7日MMX注册局系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【域名】【注册局维护通知】

      维护时间:北京时间2020年7月7日 13:00 - 15:00

      维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

      维护影响:届时 .Vip/.Work/.Beer/.Luxe/.Fit/.Yoga 域名的注册、续费、信息修改,线上操作和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

      1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

      2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

      如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

      由此给您带来的不便,我们表示歉意,敬请谅解。

      ]]>
      【升级】7月8日UNR注册局系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【域名】【注册局维护通知】

      维护时间:北京时间2020年7月8日 01:00 - 02:00

      维护内容:接到注册局的通知,注册局将于上述时间对后台系统进行维护升级。

      维护影响:届时.link/.help/.gift/.lol/.photo/.mom/.click/.pics/.game 等域名的注册、续费、信息修改和查询域名注册信息等操作,将会无法使用,在此期间会对您造成的影响如下:

      1、您提交的注册(购买)、续费、转入、赎回、一口价域名业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态;

      2、维护过程中您无法对域名注册信息进行修改,将提示修改失败。

      如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

      由此给您带来的不便,我们表示歉意,敬请谅解。

      ]]>
      【升级】7月7日DDoS高防(国际)升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【DDoS高防(国际)】【升级通知】

      升级窗口:北京时间 2020年7月7日 06:00- 07:00

      升级内容:DDoS高防(国际)进行网络升级操作

      升级影响:升级期间,部分IP需要重新连接,会导致TCP连接闪断2次。闪断对短连接和具备自动重连的长连接业务基本无影响,请确保您在业务上做好重连重试机制,以增强业务的容错能力。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】7月7日、8日阿里云域名注册系统维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【域名】【域名注册系统维护通知】

      维护时间:北京时间2020年7月7日 23:00 -7月8日 02:00

      维护内容:阿里云域名注册系统维护。

      维护影响:届时各域名的注册、续费、转入和赎回等操作将会无法使用,在此期间会对您造成的影响如下:

      1、您提交的注册(购买)、续费、转入、赎回、一口价域名等业务在支付费用后状态为“处理中”,待维护结束后将变为正常的“成功”状态。

      2、您提交的域名万网预订等业务订单处理将延迟,支付费用后暂时无法在控制台内查看到,待维护结束后将变为正常的“处理中”或其他实际状态。

      3、维护过程中您无法对域名注册信息进行修改,将提示修改失败。如果您需要注册或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

      由此给您带来的不便,我们表示歉意,敬请谅解。

      ]]>
      【漏洞预警】F5 BIG-IP TMUI 远程代码执行漏洞(CVE-2020-5902) Fri, 20 Jun 2025 02:20:33 +0800

      2020年7月3日,阿里云应急响应中心监测到 CVE-2020-5902 F5 BIG-IP TMUI 远程代码执行漏洞。


      漏洞描述

      F5 BIG-IP 是美国 F5 公司的一款集成了网络流量管理、应用程序安全管理、负载均衡等功能的应用交付平台。2020年7月1日,F5官方公布流量管理用户界面(TMUI)存在 前台远程执行代码(RCE)漏洞(CVE-2020-5902)。攻击者利用该漏洞,构造恶意请求,在未授权的情况下获得目标服务器的权限,实现远程代码执行。阿里云应急响应中心提醒F5 BIG-IP TMUI 用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      F5 BIG-IP < 15.1.0.4

      F5 BIG-IP < 14.1.2.6

      F5 BIG-IP < 13.1.3.4

      F5 BIG-IP < 12.1.5.2

      F5 BIG-IP < 11.6.5.2


      安全建议

      建议将F5 BIG-IP 升级至安全版本。下载地址参考:https://support.f5.com/csp/article/K52145254


      相关链接

      https://support.f5.com/csp/article/K52145254



      阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.07.03

      ]]>
      【漏洞预警】kube-proxy 边界限制访问绕过漏洞(CVE-2020-8558) Fri, 20 Jun 2025 02:20:33 +0800

      2020年7月10日,阿里云应急响应中心监测到 CVE-2020-8558 kube-proxy 边界限制访问绕过漏洞。


      漏洞描述

      kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件。近日Kubernetes官方发布相关安全公告,kube-proxy组件在iptables和ipvs模式下均需要设置内核参数 net.ipv4.conf.all.route_localnet=1, 从而允许本地回环访问。攻击者可能通过共享主机网络的容器,或在集群节点上访问同一个LAN或二层网络下的相邻节点上绑定监听了本地127.0.0.1端口的TCP/UDP服务,从而获取接口信息。如果服务没有设置必要的安全认证,可能会造成信息泄露风险。阿里云应急响应中心提醒 kube-proxy 用户尽快采取安全措施阻止漏洞攻击


      漏洞评级

      如果集群API Server开启了非认证端口(默认8080),那么攻击者可能获取到API Server接口相关信息,漏洞评级为高危,评分为8.8分

      如果集群API Server默认关闭了非认证端口,漏洞评级为中危,评分为5.4分。


      安全版本

      kube-proxy >=1.18.4

      kube-proxy >=1.17.7

      kube-proxy >= v1.16.11


      影响版本

      kube-proxy v1.18.0~v1.18.3

      kube-proxy v1.17.0~v1.17.6

      kube-proxy <= v1.16.10

      当前ACK集群节点中默认监听127.0.0.1的系统服务是需要认证的,且API Server也统一禁用了非认证端口。唯一暴露的是kubelet 10255的非认证只读端口,由于kubelet只读接口同样监听在0.0.0.0,因此即使修复了该漏洞在本地或特权容器中也同样可以获取到接口信息。综上,该CVE对ACK集群影响不大


      安全建议

      1. 当前ACK集群默认关闭API Server 8080非认证端口,请勿手动开启该端口。

      2. 执行以下命令,在集群中配置iptables规则,用于拒绝非本地对127.0.0.1的访问流量。

       iptables -I INPUT --dst 127.0.0.0/8 ! --src 127.0.0.0/8 -m conntrack ! --ctstate RELATED,ESTABLISHED,DNAT -j DROP
      
      
      
      
      
      
      
      

      3. 严格控制集群节点共享主机的登录权限,及时吊销可能泄露的kubeconfig集群访问凭证。

      4. 禁止Container开启CAP_NET_RAW能力,执行以下命令,可以在pod spec中关闭Container的CAP_NET_RAW能力。

      securityContext:
            capabilities:      
                    drop: ["NET_RAW"]
      
      
      
      
      
      
      

      5. 通过PodSecurityPolicy策略限制部署特权或共享主机网络容器,另外可以通过在策略中配置requiredDropCapabilities强制容器部署关闭CAP_NET_RAW能力。


      阿里云容器服务Kubernetes版不受该漏洞影响。


      相关链接

      https://github.com/kubernetes/kubernetes/issues/90259




      阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.07.10

      ]]>
      【漏洞预警】Citrix 系列产品 多个高危漏洞 Fri, 20 Jun 2025 02:20:33 +0800

      近日,阿里云应急响应中心监测到 Citrix 官方披露包含权限绕过,文件读取在内的多个高危漏洞,影响包括Citrix ADC、Citrix Gateway在内的多个产品。


      漏洞描述

      Citrix是一套提供网络管理,防火墙,网关等功能的集成化平台。Citrix 官方于近日披露包含权限绕过,文件读取在内的多个高危漏洞。攻击者通过构造恶意请求包,能够对多个Citrix产品造成包括任意文件下载、任意文件上传、代码注入、权限提升等多种影响。阿里云应急响应中心提醒 Citrix 用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      Citrix ADC and Citrix Gateway < 13.0-58.30

      Citrix ADC and NetScaler Gateway < 12.1-57.18

      Citrix ADC and NetScaler Gateway < 12.0-63.21

      Citrix ADC and NetScaler Gateway < 11.1-64.14 

      NetScaler ADC and NetScaler Gateway < 10.5-70.18

      Citrix SD-WAN WANOP < 11.1.1a

      Citrix SD-WAN WANOP < 11.0.3d

      Citrix SD-WAN WANOP < 10.2.7

      Citrix Gateway Plug-in for Linux <  1.0.0.137


      安全建议

      建议将 Citrix对应产品 升级至安全版本。下载地址参考:https://support.citrix.com/article/CTX276688


      相关链接

      https://www.citrix.com/blogs/2020/07/07/citrix-provides-context-on-security-bulletin-ctx276688/

      https://dmaasland.github.io/posts/citrix.html


      阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测



      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.07.11

      ]]>
      【漏洞预警】SAP NetWeaver AS Java 高危漏洞(CVE-2020-6287) Fri, 20 Jun 2025 02:20:33 +0800

      2020年7月14日,阿里云应急响应中心监测到 CVE-2020-6287 SAP NetWeaver AS Java高危漏洞。


      漏洞描述

      SAP是SAP公司的产品企业管理解决方案的软件名称。SAP官方发布安全更新,修复了一个存在于SAP NetWeaver AS Java(LM配置向导)7.30至7.50版本中的严重漏洞CVE-2020-6287。未经身份验证的远程攻击者可以通过创建具有最大特权的新SAP用户,绕过所有访问和授权控制,从而完全控制SAP系统。阿里云应急响应中心提醒 SAP 用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      SAP NetWeaver AS JAVA (LM 配置向导) Versions = 7.30, 7.31, 7.40, 7.50

      其中潜在受影响的SAP解决方案包括(但不限于):

      SAP Enterprise Resource Planning(ERP)

      SAP Product Lifecycle Management

      SAP Customer Relationship Management

      SAP Supply Chain Management(SCM)

      SAP Supplier Relationship Management

      SAP NetWeaver Business Warehouse

      SAP Business Intelligence

      SAP NetWeaver Mobile Infrastructure

      SAP Enterprise Portal

      SAP Process Orchestration/Process Integration

      SAP Solution Manager

      SAP NetWeaver Development Infrastructure

      SAP Central Process Scheduling

      SAP NetWeaver Composition Environment

      SAP Landscape Manager


      安全建议

      建议将 SAP对应产品 升级至安全版本。下载地址参考:https://wiki.scn.sap.com/wiki/pages/viewpage.action?pageId=552599675


      相关链接

      https://us-cert.cisa.gov/ncas/alerts/aa20-195a



      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.07.14

      ]]>
      【漏洞预警】Windows DNS Server远程代码执行漏洞(CVE-2020-1350) Fri, 20 Jun 2025 02:20:33 +0800

      2020年7月14日,阿里云应急响应中心监测到微软发布补丁修复了一个标注为远程代码执行的DNS Server漏洞(CVE-2020-1350),官方分类为“可蠕虫级”高危漏洞。目前微软官方已提供临时缓解措施以及相应的月度安全补丁修复该漏洞。


      漏洞描述

      微软官方于7月14日发布安全更新,其中修复了一个标注为远程代码执行的DNS Server漏洞(CVE-2020-1350),官方分类为“可蠕虫级”高危漏洞。未经身份验证的攻击者可以发送特殊构造的数据包到目标DNS Server来利用此漏洞,成功利用此漏洞可能达到远程代码执行的效果。如果域控制器上存在DNS服务,攻击者可利用此漏洞获取到域控制器的系统权限。阿里云应急响应中心提醒 Windows 用户尽快采取安全措施阻止漏洞攻击。


      漏洞评级

      CVE-2020-1350 高危


      影响版本

      Windows Server 2008 for 32-bit Systems Service Pack 2

      Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core)

      Windows Server 2008 for x64-based Systems Service Pack 2

      Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core)

      Windows Server 2008 for 32-bit Systems Service Pack 2

      Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core)

      Windows Server 2008 for x64-based Systems Service Pack 2

      Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core)

      Windows Server 2008 R2 for x64-based Systems Service Pack 1

      Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core)

      Windows Server 2008 R2 for x64-based Systems Service Pack 1

      Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core)

      Windows Server 2012

      Windows Server 2012 (Server Core)

      Windows Server 2012

      Windows Server 2012 (Server Core)

      Windows Server 2012 R2

      Windows Server 2012 R2 (Server Core)

      Windows Server 2012 R2

      Windows Server 2012 R2 (Server Core)

      Windows Server 2016

      Windows Server 2016 (Server Core)

      Windows Server 2019

      Windows Server 2019 (Server Core)

      Windows Server, version 1903 (Server Core)

      Windows Server, version 1909 (Server Core)

      Windows Server, version 2004 (Server Core)


      安全建议

      1、缓解措施。修改 HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesDNSParameters 中TcpReceivePacketSize的值为0xFF00,并重启DNS Service。

      2、前往微软官方下载相应补丁进行更新 https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350

      3、阿里云云安全中心Windows系统漏洞模块已支持对该漏洞补丁一键检测和修复,详情登陆云安全中心

      Windows Server 2008 (官方已停止免费补丁维护,需购买微软ESU服务,建议放弃使用:https://www.microsoft.com/en-us/windows-server/extended-security-updates

      Windows Server 2012 补丁:KB4565537

      Windows Server 2012 R2 补丁:KB4565541

      Windows Server 2016 补丁:KB4565511

      Windows Server 2019 补丁:KB4558998




      相关链接

      https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/

      https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350



      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.07.15

      ]]>
      【其他】7月13日堡垒机升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【堡垒机】【升级通知】

       1、升级窗口:

       7月13日-7月24日 (分批次升级);

       2、升级区域:全区域

       3、升级内容:

      堡垒机产品V3.2.X版本升级至V3.2.10版本,V3.2.10版本在主机配置、用户配置、控制策略、支持语言等均进行了优化升级:  

           1)主机配置:支持从文件导入主机;支持RDS专有主机组导入;支持同步时自动更新ECS属性;支持批量新增主机账号密钥;支持主机指纹;支持手动修改主机操作系统

            2)用户配置:新增堡垒机本地用户;支持从文件导入本地用户;支持AD/LDAP认证;支持配置用户SSH公钥;支持用户的短信双因子认证;支持国际手机号(+86、+852、+7、+60、+62、+65)

            3)控制策略:支持配置命令、协议访问、来源IP控制策略;支持命令审批

            4)支持英文、中文繁体、中文简体切换

      ]]>
      云上安全等保2.0解决方案-米姆带你轻松过等保测评 Fri, 20 Jun 2025 02:20:33 +0800 适用客户


      需要按照等级保护保护标准和规范进行建设、整改的各行业与单位。

      主要内容


      公司等级保护建设、整改业务主要内容包括如下:


      安全规划:根据国家等级保护相关要求等,结合客户的组织架构、业务要求、实际情况,整理客户安全建设工作实际需求和目标,形成《调研报告》和《总体规划》;


      定级备案:确定客户的定级范围,分析客户的组织架构、业务要求等内容,进行摸底调查,确定定级对象;针对定级对象,基于国家《定级指南》协助系统主管部门初步确定系统的等级,形成《定级报告》;协助进行专家评审和审批,协助完成定级备案工作;


      风险评估:通过资产评估、漏洞扫描、审计、网络架构分析、数据流分析等方式,为客户全面分析信息系统的资产现状、主机、数据库、安全设备、网络的弱点、威胁和风险,形成《风险评估报告》(可选增值服务);


      差距分析:按照等级保护相应级别的安全要求,对需要建设的系统进行安全体系等方面的差距分析,形成《差距分析报告》;


      安全建设:根据确认的等级保护差距报告分析情况,制定整体的《解决方案》,协助方案评审,协助落实等级保护安全建设实施工作;


      整改加固:根据相应级别等级保护对应的相应要求,制定所需安全管理制度、流程和表格、技术标准和规范等文档,并对主机、网络设备、数据库制定加固方案,通过打补丁、修改安全配置、增加安全机制等方法,合理加强设备的安全性,以满足等级要求;


      辅助测评:为了更好的协助客户通过等级保护测评工作,提供辅助测评支持;


      安全运维:主要包括安全巡检、风险评估、安全加固、应急响应及其他客户需要的安全运维工作,更好的确保系统稳定、安全的运行。


      米姆为您提供一站式等保评测服务,有需要请速速与我们联系吧。

      ]]>
      短视频程序近年来为何如此的受欢迎,都有哪些吸引人的功能? Fri, 20 Jun 2025 02:20:33 +0800 一说到短视频程序,相信大家都不陌生,它已经渗入到我们的生活中,当今的大众群体,无论80后90后,还是部分70后都有了刷短视频的习惯,去消磨自己的闲暇时光,很多软件公司也纷纷涌入开发。在开发短视频小程序之前首先要了解一个问题,为什么短视小程序能够如此受欢迎呢?有人会说好玩,那么短视频程序到底为何受欢迎,以及都有哪些吸引人的功能呢。下面为大家简单整理了几点原因。
      直播1_副本.jpg

      一、为何受欢迎
      短视频的快速发展也带动了电商的复苏,也有些人发现了其中的商机,发现了短视频与电商相结合的模式,打开了短视频带货的大门,也为疫情当下的实体店铺和电商开辟了新路径。
      内容丰富多元化,用户体验度高,能够分析大数据,根据大众的喜好喜欢去推送相关的内容信息,做到“私人订制”。打开短视频程序,无不有许多种类的短视频,唱歌、搞笑等其他有意思的,一个接着一个让人过不完的瘾。“搞笑”“正能量”“新闻”“长知识”等等内容,其中搞笑类的占据多数,不少用户本着寻个开心的想法都能在短短15秒的时间内得到满足。平台内容迎合大众喜爱,因此这是短视频深受大众喜爱的最关键原因。
      能够解决碎片化时间需求,每个人每天都会有充足的碎片化时间,而这些碎片化时间基本上与无聊为伴,而短视频小程序能够提供有趣的内容,为观看者解决碎片化时间的需求。
      直播2_副本.jpg

      社会新风向,不管是哪个行业的兴起,都离不开借助社会环境这个重要因素,而政府一向都很支持文化创意产业的发展,所以短视频的兴起也成为了大势所趋。得到了政府和人民的支持,短视频也就等于获得了社会和大众的认可。
      视频虽“短”,但粘性强。“短视频”不同于长视频甚至是专业的记录视频,小视频在时间上比较的短小,有的一般都在15秒,甚至10秒。这样的时间里展现的短小的视频,一般人浏览不会产生视觉疲劳,一个视频十几秒就过了,可以快速进入下一个新的视频里去。在以快节奏阅读的时代里,这种形式是比较让人接受,受到欢迎的。视频虽“短”,但有一定粘性,人们会不自觉的滑动屏幕长时间浏览。
      好的短视频也是建立在功能实用性和多样化的基础上,所以说一款短视频程序的开发也缺少不了功能的实现,那么一款短视频小程序有什么功能呢?
      直播3_副本.jpg

      二、短视频小程序有什么功能?
        1、在线观看:用户可以直接通过小程序进行观看短视频的相关内容,只需要打开小程序就可以直接浏览。
        2、在线评论:当用户在浏览过程中,对于自己感兴趣的内容也可以发表相应的评论。
        3、在线发布:每一位用户既然视频的浏览者也可以是创作者,为了激励他们创作,平台会进行一定的奖励。
      4、在线关注:为了第一时间获取自己想要浏览的内容,用户可以直接关注相应的创作者。
      声明:以上内容为云豹科技作者本人原创,未经作者本人同意,禁止转载,否则将追究相关法律责任

      ]]>
      数据中台模型设计系列(一):维度建模初探 Fri, 20 Jun 2025 02:20:33 +0800 前言:更多关于数智化转型、数据中台内容可扫码加群一起探讨
      668d7f5941782665ed1f41529db3eb677f4b9379.png
      阿里云数据中台官网 https://dp.alibaba.com/index


      1、与几个概念的关系

      操作型业务系统
      对于这个概念大家都不陌生。企业业务赖以运转的交易系统就属于操作型业务系统。因此它是为了保障业务正常运转,能够更快的处理事务。

      但是因为它是针对某一特定的意图(例如满足交易业务),它不需要承诺与其他业务系统共享公共数据。因此就出现了适合于企业中交叉应用的ERP、主数据系统。当然对于有建设业务中台的企业来说,基于微服务架构的各个服务中心,能更好的提供可复用统一的公共数据。

      不管是面向业务的业务系统、经过数据统一后的主数据系统或者基于微服务架构的服务中心的数据,都是作为数据中台的数据输入源头。我们通过批量同步、归档日志采集等方式,能将数据采集进数据中台,作为ODS层原始数据的一部分。

      ETL
      英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。在ODS层的原始数据,需要通过加工处理后,才能进入到构建好的数据模型中。

      在模型设计时,需要考虑ETL加工流程,根据逻辑判断,做模型的合理设计。同样对于下游使用数据模型的ETL元数据,也是作为模型设计的输入,可基于下游应用方式做模型的横向和纵向的拆分设计,这就是“元数据驱动模型设计”的理论来源。

      因此,无法理解数据开发的模型设计师是不合格的。

      数据应用
      数据中台提供多种数据应用的形式,包括数据报表、智能数据产品等。将统一汇总加工后的数据或者明细原子数据提供给数据应用,为业务提供数据支撑。

      更加合理的数据模型设计,能够给更宽泛的应用提供数据支撑,也能够让业务方更准确无疑义的使用好数据。

      2、几种企业常见的建设现状

      烟囱式
      也许大家都不愿意承认,但是绝大部分的企业当前是没有统一、标准、公共、全局的模型设计的,而仅仅是把数据同步上来,然后基于业务需求做烟囱式的数据开发。这种方式也许从短期来看是效率最高的,但是从长期看,不仅仅造成计算存储资源的极大浪费、没有统一可用的数据、大量的重复性的工作。企业的数据就像一团乱麻,根本无法管理。

      三范式+数据集市

      一些传统大型企业,由于历史原因,原子数仓中以三范式的模型设计方式构建,在各个应用的数据集市中以维度建模方式构建。通过这种方式,在原子数据设计过程中,需要投入较大的资源。

      对于业务来说,三范式模型太复杂,用户难以理解和检索。并且对于业务频繁变化的企业,模型的维护成本极高。

      企业级维度模型

      基于企业全局的角度去构建业务总线矩阵,在此基础上完成维度模型的设计,是当前众多企业选择的方向。从众多互联网企业的数据中台实践经验来看,这也是一个绝佳的各因素平衡后的选择。

      后面,我们将从各个角度来思考如何基于维度模型构建企业级数据中台。

      3、维度建模初探

      优势
      在数据中台建设经验中,企业级维度模型设计从理解性、扩展性、高性能上都是更适应当前的技术和业务环境的。

      首先由于计算和存储成本逐步下降,模型更重要的变成了易于理解,当易用性放在模型设计的重要位置时,维度模型可理解的优势就显现出来了,维度建模一直就是以业务的视角来描述数据。

      另外,当新的业务出现时,新的模型不会对已有模型形成冲击,可以无影响的产出新的模型数据。

      维度建模会设计部分数据的冗余,通过冗余换来数据检索的高性能。对于数据量极具膨胀的今天,高性能给用户带来了高价值。

      事实表

      所谓的事实表,就是企业的业务过程事件的度量信息。例如对于支付这个业务过程来说,需要度量支付的商品数、金额等度量。因此,企业的业务过程数据以事实表的形式在模型中呈现出来。

      事实表每行都对应了一个度量事件,每行数据是一个特定级别的细节数据。事实表中每个度量都必须是相同的粒度级别。

      事实表中的度量的可加性也至关重要,因为业务方往往需要将事实表的数据基于某些维度进行汇总,在度量上需要能够做汇总累加。

      事实表还是稀疏的,它仅仅会将发生的业务过程数据放入其中。
      **
      维度表**

      维度表是事实表不可或缺的组成成分,它描述了事实表业务过程度量的环境。用于描述“谁、什么、哪里、何时、如何、为什么”有关的事件。

      维度属性是作为查询约束、分组、标识的主要来源,因此它的好坏直接决定了数据的可分析性的差异。维度属性需要是可理解的,因此需要尽量避免“0,1”之类的代码,将代码翻译成更易理解的字符避免业务的误解。

      同样,会有一些数值型的可作为维度属性。例如:也许有人会问商品标价适合在事实表还是维度表中?

      当用于计算度量时,它应该存在于事实表中;但是当它用于做约束、分组、标识分析时,则需要存在于维度表中。在维度表中,我们往往会把连续的数据换成离散的数值存储,例如:将标价变为价格区间段。这是要根据对业务的理解做进一步设计的。

      雪花模型与星型模型

      所谓的雪花模型,是当有一个或多个维表没有直接连接到事实表上,而是通过其他维表连接到事实表上时,其图解就像多个雪花连接在一起,故称雪花模型。

      而星型模型则是所有维表都直接连接到事实表上,整个图解就像星星一样,故将该模型称为星型模型。

      雪花模型是对星型模型的扩展。

      星型模型是一种非正规化的结构,多维数据集的每一个维度都直接与事实表相连,不存在渐变维度,所以数据有一定冗余。因为有冗余,所以很多统计不需要做外部的关联查询,因此一般情况下效率比雪花模型高。

      但是从可理解性上看,雪花模型是更容易让业务理解的。因为业务可以从模型上看出维度与维度之间的关系。

      因此如何平衡查询效率和业务理解?我们在后面的文章中再细细道来。

      **总线矩阵
      **
      总线矩阵,维护的是企业的各个业务过程与一致性维度的关系。是以企业的高度实现的顶层设计。它的存在对于数据中台项目至关重要。

      如果数据中台的模型设计就是一本书,那么总线矩阵就是这本书的目录,能从整体上对每个模型有统一的定义。

      从项目协调上看,总线矩阵在大型项目中起到举足轻重的地位,整个项目组都能基于这个目录清晰的明白自己在做什么,别人已经做了什么,极大程度上的避免了信息沟通不畅导致的重复定义。

      从项目管理上看,也可以基于总线矩阵对模型设计和开发进行有效的优先级排期。

      最后,总线矩阵是共同业务人员和技术人员的桥梁,通过总线矩阵在项目沟通中达成一致的语言。

      结语

      通过这篇文章,初浅的对数据中台模型设计发表了一些观点。
      在后面的章节中,我们将继续围绕模型设计的技术细节、结合行业的模型设计案例,和数据同仁们做进一步的分享和交流 。


      数据中台是企业数智化的新基建,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。目前正通过阿里云数据中台解决方案对外输出,包括零售金融互联网政务等领域,其中核心产品有:

      官方站点:
      数据中台官网 https://dp.alibaba.com
      数据中台钉钉群二维码2.jpg


      ]]>
      小白使用阿里云建网站三种方式(自助建站+模板建站+功能定制建站) Fri, 20 Jun 2025 02:20:33 +0800


      使用阿里云建网站的三种方式购买云服务器手动建站、云速成美站模板建站或者选择阿里云定制建站三种方式,站长分享利用阿里云创建网站的三种方式及优势对比:


      阿里云建站方法汇总


      使用阿里云建站可以有三种方式,


      第一种是购买ECS云服务器,然后自行手动搭建网站,需要技术门槛;


      第二种方式是购买阿里云官网云速成美站,使用模板建站,阿里云提供上千套模板,模板建站价格便宜,会打字就会建站;


      第三种是使用阿里云官方定制建站,需要什么样的网站什么功能,阿里云建站专家提供一对一网站定制。参考下表:

      阿里云建站方式 所需产品 优势 适用人群
      自助建站 ECS云服务器 自行购买云服务器,手动搭建网站 需要些技术门槛,适用于刚接触云计算或对云服务器和建站不太了解、希望自行设计网站的个人或小企业用户。
      模板建站 云·速成美站 使用阿里云提供上千套模板,可视化后台管理,会打字就会建站 适合有一定软件应用能力的个人或小企业用户,模板建站支持Web站点、移动端站点、互动表单以及会员支付多场景。
      定制建站 云·企业网站定制功能定制 由阿里云专业网站设计师完成网站设计及搭建 适合对网站有品质要求或个性化需求、希望节省人力和时间成本的企业用户。

      阿里云建站产品如何选择?如果您是站长类的技术人员,当然选择自助建站方式,如果非技术人员,个人或者工作室建议选择云·速成美站,如果是企业用户建站选择阿里云网站定制服务。


      阿里云建站不需要用户另外购买云服务器或虚拟主机等产品,阿里云提供香港节点并且提供全球CDN加速,不用备案,拿来即用。阿里云大品牌无隐形消费,我见过太多打着免费建站的幌子,实际价格贵的离谱。举例来说,免费建站,使用的域名是对方的三级域名,域名人家说收回就收回,免费建站,云主机却要收费,而且价格很贵没有质量保障,网站说打不开就打不开。


      我从新手过来的,之前使用过免费域名,用了有一段时间了,结果被收回了,使用免费虚拟主机,速度卡不说,结果网站数据丢失了,这不是免费惹的祸,是小编贪图便宜惹的祸,建议选择大品牌,值得信赖。


      ]]>
      首次揭秘!​春晚活动下快手实时链路保障实践 Fri, 20 Jun 2025 02:20:33 +0800

      摘要:本文由快手开发工程师刘建刚分享,主要介绍春晚活动下快手实时链路保障实践。内容主要包含以下四部分:

      1. 快手 Flink 简介
      2. 春晚实时保障方案
      3. 春晚实时大屏
      4. 未来规划

      Tips:点击「阅读原文」链接可查看作者原版 PPT 及分享视频~

      一、快手 Flink 简介

      我们首先来看一下快手的实时计算架构图。主要分为4个部分,包括数据接入、数据计算、数据应用和数据展示。各层职责分明、衔接顺畅,方便用户开发。

      640 1.png

      快手的 Flink 集群规模大概有 3000 多台机器,日处理条目数为20万亿,峰值为38亿条。主要应用场景包含以下四类:

      • 实时 SQL 平台,这是 Flink 托管的一个产品化的 SQL 平台。
      • 短视频、直播等指标的实时计算,涵盖了公司的主要业务和产品。
      • 机器学习的数据预处理,支撑着快手广告等各种模型的训练。
      • 快手所有的日志拆分、同步等实时的数据流。

      640 2.png

      二、春晚实时保障方案

      快手中标了2020年的央视春晚,春晚作为全球华人辞旧迎新的晚会,数据量之大前所未有。快手 Flink 作为公司的实时计算平台,支持春晚超大状态和千万并发等复杂计算。春晚项目的挑战主要体现在稳定性、实时性、准确性三个方面,我们为此制定了一系列方案为春晚保驾护航。

      640 3.png

      下面我会通过这4个方面来介绍一下我们为春晚做的努力。

      • 第一个是过载保护,主要介绍极端压力下的技术应对方案;
      • 第二个是全系统的稳定性,确保各个方面都万无一失;
      • 第三个是压力测试,它是春晚的提前模拟;
      • 第四个是资源的保障,涉及到资源的管理和保障。

      640 4.png

      1.过载保护

      Flink 在流量激增或者单点性能不足的情况下,有可能会发生卡死、雪崩或者失败的情况。这个时候一旦我们的实时作业挂掉,整个作战计划就会被打乱,可能给公司带来很大的损失。

      640 5.png

      我们针对这种场景设计了一种健康检查、智能限速、源端控制相结合的柔性可用技术。为什么要通过源端控制?首先,如果出了问题,我们可以在下游的 task 上进行控制,但是这样的话可能带来一个问题,它会造成反压等阻塞行为,有可能会把整个作业卡死,所以我们通过控制数据源来从本质上解决问题。下面是我们技术实现:

      • TaskManager 作为从节点,将自己的健康信息定期汇报到 Master 节点。
      • Master 节点一旦检测到极端压力,立刻要求所有的 source 限速 50%。
      • 如果之后作业状态良好,就会慢慢的提高我们的输入 QPS,每次 10%。

      640 6.jpg

      然后看一下我们的测试效果图。流量高峰到来时 QPS 为 200K。一旦 Master 节点检测到极端压力,直接将 QPS 限速到 100K。之后检测到作业状态良好,就逐步地进行恢复。经过测试(随着逐渐恢复各项指标会有波动),我们的 CPU 使用率从最高的 100% 降到了 80%~90%,ygc 由每分钟的10秒降到了每分钟3秒以内,同时也避免了的 oom、心跳超时、卡死等各种问题。这种技术能够保障我们 Flink 在极度压力下的存活,起到了削峰保命的效果。

      640 7.png

      我们还设计了一种轻量级的热更新模型,在作业不停止的情况下通过 restful 接口实时的控制作业去应对各种压力,避免了繁琐的修改代码、打包、上线等耗时过程。常见功能包括关闭快照、设置采样率、source 源鲜素,如下图所示。

      640 8.png

      2.全系统稳定性

      分布式系统涉及到方方面面,任何一个环节出了问题都可能是致命的,我们为此在故障应对和项目管理上做了很多工作。故障应对包含故障排除、故障演练、故障预案,项目管理包含作业管理、集群管理、工程管理。

      640 9.png

      首先进行的是 Flink 的故障排除。Flink 的交互组件包括 Yarn,HDFS,Kafka,Zookeeper,我们逐一的对每个组件进行故障排除。它们各自的风险排除步骤,如下图中的表格所示。

      640 10.png

      故障排除完了之后,就需要在真实的场景中进行故障演练。主要的演练方法就是在我们的集群中,随机的注入各种异常来测试并且完善 Flink 的稳定性,确保 Flink 做到以下保障:

      • 相关组件异常不影响 Flink,比如 Yarn 和 HDFS 的主节点挂掉。
      • 作业宕机,Hawk 监测系统5秒内发现并作相关处理。
      • 作业 Failover 在30秒以内。

      640 11.png

      为了更好地应对各种故障,我们还制定了完善的故障预案,这是一套完整的应急指导方针。针对每一种故障,我们都有快速的问题排查和解决手段。

      640 12.png

      除了快速应对故障,良好的管理手段也必不可少,它可以有效的减少故障。下面介绍我们管理上的一些手段。

      首先是作业管理这一块,包含作业管理系统的稳定性和相关的运维工作。包括四个方面:

      • 作业管理系统支持高可用。一旦出问题可以快速的切换。
      • Checklist 规范用户开发,包括快照设置和数据源对齐等实战经验。
      • 常见工具,包含全局日志查询、高负载查询、快速迁移等。
      • 报警机制和 metric 的展示,做到问题提前发现和及时发现。

      640 13.png

      接下来是集群管理。集群作为我们整个系统的物理载体,一旦出现问题就是致命性的:

      • 针对高优作业搭建多套实时集群,避免离线的干扰。
      • 关键队列物理隔离。针对特定集群要求的作业,通过物理隔离来保障其稳定性。
      • 机器环境的排查。确保机器环境符合我们的预期。
      • 重要作业实现多集群的部署,出现问题秒级切换。(实时大屏会详细介绍)

      640 14.png

      最后一个就是工程的管理。工程管理的关键是时间线预案,主要是指导我们在什么时间点该做什么事情,贯穿整个项目开发。下面简单描述了下春晚的事前、事中、事后的主要操作:

      640 15.png

      3.压力测试

      压力测试相当于春晚的一个模拟,它能够帮助我们发现很多问题。针对春晚,压力测试比一般的时候要更复杂一些。面临的最主要的两个问题,第一个问题就是数据模型怎么构造,比如说有哪些 topic、各 topic 的数据分布是怎么样的。第二个问题是我们如何根据这些模型生成符合条件的数据。

      640 16.png

      数据模型的难点在于构建的数据分布必须跟春晚保持一致。我们的解决方案是以人为参考单位,基于统计规律建立人与数据分布的映射关系。简单来说,计算出每个人在各个 Topic 的数据量,预估有多少人各个 Topic 的 QPS 就翻多少倍,当然实际情况会复杂许多。最终,我们根据公测和元旦当天用户产生的数据来进行春晚模型的建立。

      640 17.png

      模型构建完成之后,需要根据模型生成数据。为此,我们构建了一整套完善的数据生成服务,用户只需要进行简单的配置就可以,而流量控制、多 task 的协作、分布式生成等复杂的实现全部由平台实现。主要有三种形式的数据生成:

      • 数据翻倍,基于 bytes 的流量翻倍,性能最佳。
      • 时间压缩,在不改变数据分布的情况下压缩数据、提高 QPS。
      • 样本生成,根据用户提供样本生成符合条件(QPS、UV等)的数据。

      640 18.png

      4.资源保障

      春晚对数据的实时性要求比较高。只有资源保障到了才可以实现我们的实时性。

      640 19.png

      资源保障的关键策略是分级保障。我们把作业分了三个优先级:P0、P1 和 P2:

      • P0 是高优作业,跟春晚相关的一些活动。
      • P1 是重要的作业,是指业务方的一些重要作业。
      • P2 是普通的任务,一般指非核心的作业。

      为了做到分级保障,我们制定了重点保障高优先级作业的降级策略:

      • 春晚之前,我们会批量的把 P2 的任务都停止掉,把资源全部都挪给 P0 和 P1。
      • 春晚中,如果有需要,降级 P1 的资源来保障 P0 作业的资源。
      • 春晚后,我们会把之前停掉的 P2 作业再重新提起来,并且从 kafka 最新的 offset 开始消费。

      通过分级保障,在资源有限的情况下,优先保障了我们的重点作业,确保了高优任务的实时消费。分级保障对今后的服务治理意义重大。比如说以后的资源调度、灰度上线、作业迁移等等,都可以参考分级保障的策略。

      640 20.png

      资源保障的另一个体现在于快速扩容,分为实时指标监控和资源选择两个方面。实时指标监控包括作业吞吐,作业延时,快照信息,物理信息,通过这4个重要的部分就可以衡量一个作业是否健康。如果我们检测到作业有性能问题需要扩容,需要按照一定顺序选择资源来补充——集群内冗余资源,备用集群,低优任务资源。

      640 21.png

      三、春晚实时大屏

      下面我们以实时大屏作为经典案例来介绍。快手春晚实时大屏为作战指挥官提供最核心的活动和公司大盘实时数据,总共承接了100多个实时指标的计算,包括在线、红包、互动等各项指标。主要的挑战表现在三个方面:

      • 数据量大,QPS 在千万级别;
      • 实时性要求比较高,在秒级以内;
      • 稳定性要求高,可用性为4个9。

      接下来我会从技术选型、压力测试、服务部署三个方面顺序展开。

      640 22.png

      1.技术选型

      架构组件是以 Flink 为主,Redis 为辅。Flink 适合大规模的实时计算,Redis 作为一款高效的 KV 查询工具来辅助实时计算。

      各项指标中,基于 deviceld 的 UV 计算最多、复杂度最高。一般来说,先将字符串类型的 deviceId 转成数字类型的 id,然后存储在位图结构 bitmap(存储空间小)中进行计算。这里介绍下快手的技术方案:

      • Flink+HBase。HBase 负责将 deviceId 转成 id,Flink 负责计算。
      • Flink+Redis。通过 Redis 判断 deviceId 是否首次出现,如果是就下发计算。
      • Flink 自身。Flink 自建字典为快手内部实现的精准一次方案。

      前面两种方案将字典和计算解耦,适合多个作业共享一个字典。最后一种方式不再依赖外部系统,性能最佳。实时大屏根据自己需求和熟练度选择了第二种方案。

      640 23.png

      2.压力测试

      压力测试分为单作业的压测和全链路的压测。

      • 单作业压测这一块,我们通过增量计算、批量访问、本地缓存、objectReuse 等优化技术,使单个作业的 QPS 达到百万级别。
      • 全链路压测这一块,用来评估整体性能,需要协调所有数据和作业,具体操作如下所示。

      640 24.png

      3.服务部署

      最后一步是多链路的服务部署。实时大屏作业的稳定性要求特别高,必须多链路热备:

      • 双机房热备。一旦机房挂掉,立刻切到另一个机房。
      • 多链路热备。针对全量链路和采样链路,集群内物理隔离、多链路运行。
      • 指标故障用户无感知。最上面视图层屏蔽底层链路,底层出问题可以秒级切换。

      640 25.png

      四、未来规划

      上面是春晚实时大屏案例的介绍,下面也跟大家分享一下我们将来的一些规划:

      • 推广 SQL,探索批流统一、Table&Stream 的广泛用途。
      • 自研 SlimBase statebackend,实现存储计算分离。
      • 提升 Flink 故障自愈能力。
      • 建立作业诊断模型,实现问题快速定位。
      • 探索数据库、K8s 等相关系统的组合应用。

      文章不够看?点击「阅读原文」可直接回顾作者现场分享的讲解视频~

      ]]>
      com.alibaba.dubbo.rpc.RpcException: No provider available from registry stable.zk.scsite.net:2181异常解决 Fri, 20 Jun 2025 02:20:33 +0800 原文链接:
      https://copyfuture.com/blogs-details/20200629181409555v24mc4magq748ni

      最近发现dubbo消费端一直报下面这样的error日志

      06-17 15:56:50.749  ERROR default - [orSendTimer-thread-1] c.alibaba.dubbo.monitor.dubbo.DubboMonitor :  [DUBBO] Unexpected error occur at send statistic, cause: No provider available from registry *.*.*.*:端口 for service com.alibaba.dubbo.monitor.MonitorService on consumer 172.*.*.124 use dubbo version 2.8.3, please check status of providers(disabled, not registered or in blacklist)., dubbo version: 2.8.3, current host: 172.*.*.124
      com.alibaba.dubbo.rpc.RpcException: No provider available from registry stable.zk.scsite.net:2181 for service com.alibaba.dubbo.monitor.MonitorService on consumer 172.*.*.124 use dubbo version 2.8.3, please check status of providers(disabled, not registered or in blacklist).
          at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:577)
          at com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:74)
          at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.list(AbstractClusterInvoker.java:271)
          at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:232)
          at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:75)
          at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:77)
          at com.alibaba.dubbo.common.bytecode.proxy19.collect(proxy19.java)
          at com.alibaba.dubbo.monitor.dubbo.DubboMonitor.send(DubboMonitor.java:127)
          at com.alibaba.dubbo.monitor.dubbo.DubboMonitor$1.run(DubboMonitor.java:72)
          at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
          at java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:308)
          at java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
          at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
          at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
          at java.lang.Thread.run(Thread.java:748)

      该个error对项目的功能没有任何影响,但反复的报错也是让人心烦,那么就解决吧。

      原因

      没有启动监控中心,却配了监控地址。

      解决方案

      1. 把监控中心启动
      2. 把xml配置中的
      3. properties配置中的dubbo.monitor.protocol=registry去掉

      dubbo:monitor

      监控中心配置。对应的配置类: org.apache.dubbo.config.MonitorConfig

      | 属性 | 对应URL参数| 类型| 是否必填| 缺省值| 作用 | 描述 | 兼容性 |
      | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
      | protocol | protocol| string| 可选| dubbo| 服务治理 |监控中心协议,如果为protocol="registry",表示从注册中心发现监控中心地址,否则直连监控中心。| 2.0.9以上版本|
      |address| | string |可选| N/A |服务治理|直连监控中心服务器地址,address="10.20.130.230:12080" | 1.0.16以上版本

      ]]>
      数据库产品事业部月刊(2020.5) Fri, 20 Jun 2025 02:20:33 +0800 一、重大事件

      1、自研云原生数仓AnalyticDB包揽权威测评TPC-DS、TPC-H全球第一!

      5月4日,TPC(全球最知名非盈利的数据管理系统评测基准标准化组织)官网正式上线AnalyticDB TPC-DS成绩,AnalyticDB通过严苛的TPC-DS全流程测试,性能QphDS分数为14895566,性价比分数为0.08CNY,相比较基于Spark深度优化版的前世界纪录性能提升29%并且单位成本仅为其1/3,成为TPC-DS官方榜单上全球性能、性价比双双领先的数据仓库,这是继2019年4月26日后再次获得全球领先的成绩!

      666.png

      此外,AnalyticDB首次刷新TPC-H 30TB当前有效榜单纪录成为全球第一,性能、性价比全球领先!相比前世界纪录 微软SQL Server 2019,综合性能提升290%,单位成本仅为其1/4,成为中国首次荣登该榜单的产品!

      000001.png


      查看详情:https://developer.aliyun.com/article/759564

      2、开启云原生数据库新时代,阿里云数据库启用全新品牌口号

      5月18日,阿里云数据库产品事业部(ApsaraDB)宣布正式启用全新品牌口号 —— “阿里云数据库:更快、更稳、更安全(Faster, Stronger, Securer)”,未来将聚焦 “极致性能”、“稳定性”和“安全性” 三大品牌价值内核,在多个数据库细分领域,为用户提供业界领先的数据库产品和最专业的数据库技术服务。新品牌口号的提出,标志着阿里云数据库的全面升级,开启云原生数据库的新时代,同时也是更好地为客户服务的信心体现。

      飞刀揭幕.JPG

      查看详情:https://developer.aliyun.com/article/761835

      二、重大产品更新

      PolarDB下一代云原生数据库

      RDS MySQL用户升级到PolarDB可保留原地址(应用程序不改连接配置)

      RDS MySQL实例可直接升级为PolarDB MySQL,3分钟停机时间,业务0改动。在升级的过程中,既可以保留原来RDS主实例地址(包含公网地址/私网地址),也可以保留RDS的读写分离地址,大大降低了用户使用PolarDB的门槛,上线区域包含中国站和国际站。

      国际站PolarDB上线存储包,10T及以上存储空间可节省40%成本

      PolarDB在国际站现已支持包年包月购买存储包,用来抵扣PolarDB集群的存储费用,通过存储包可大幅节省用户的存储成本,尤其适合大容量用户。

      PolarDB 备份功能重大升级并收费

      PolarDB 面向全球所有地域的用户开放更多高级配置选项和能力,满足企业级数据备份的需求,进一步保障数据安全。支持调整备份周期;支持永久保留数据库备份;支持数据库回收站,可以从回收站恢复已释放实例,挽救误操作;备份存储分级,二级备份极大幅降低用户保存历史备份数据的成本。上线渠道包含中国站和国际站,以及金融云、新零售云聚石塔、菜鸟云等虚商。

      云原生数据仓库+数据湖

      云原生数据仓库弹性模式规格实例发布

      ADB MySQL弹性规格上线。实现了资源层面的弹性购买,用户可按照计算资源、数据资源独立购买,存储方面无需购买按量付费。目前开放了国内区域,预计6月底开放海外部分。

      云原生数据仓库基础版支持ESSD云盘

      ADB MySQL基础版(单机版)规格实例的存储系统在支持高效云盘的基础上,新加ESSD云盘存储,支持高效云盘月ESSD圆盘存储之间相互切换。基于ESSD云盘存储的ADB MySQL集群读写性能有巨大提升,进一步降低用户成本。

      云原生数据仓库PostgreSQL版推出资源弹性模式

      分析型数据库AnalyticDB for PostgreSQL正式推出资源弹性模式。节点规格支持2C16G和4C32G两种规格,节点数可从4~256选择。同时支持ESSD云盘和高效云盘两种存储形态,空间支持50~1000GB范围选择,且支持在线扩容。通过支持资源弹性模式,AnalyticDB for PostgreSQL将为用户带来更加灵活的选择,可根据空间使用按需扩展使用,大幅降低用户前期投入。

      云数据库ClickHouse支持SLS日志投递发布

      云数据库ClickHouse 接入SLS 日志投递功能,支持日志数据实时投递到ClickHouse进行实时分析。为新零售,电商, 直播,游戏等行业客户构建低成本高性能的实时日志分析系统提供产品解决方案,支撑用户画像分析,用户行为分析,人群划分等数据化运营业务。

      云数据库ClickHouse 接入数据管理服务DMS发布

      云数据库ClickHouse 接入数据管理服务DMS,支持在线数据库表管理,在线查询和任务编排调度,避免客户下载和安装Client 的复杂开发流程, 提升了用户体验,降低了数据分析开发的成本。

      RDS&NoSQL&管控工具

      阿里云数据库专属集群国际站发布

      阿里云数据库专属集群国际站发布,支持MySQL:杭州、北京、上海、深圳、张家口、呼和浩特、香港、新加坡、雅加达、孟买、硅谷等地域已开服。

      MySQL 库表级恢复效率提升四倍

      RDS MySQL 高可用版本地盘实例,包括5.6、5.7、8.0 三个版本,单库单表级恢复效率提升四倍,一般情况下可达100MB/S的恢复速度,即相当于1分钟可以恢复6GB的单表。 本次优化对于如游戏类业务场景非常友好,可以实现快速的开服回滚等业务操作。

      RDS SQL Server支持SSD磁盘盘升级ESSD

      RDS SQL Server 高可用系列支持SSD磁盘盘升级ESSD,客户可直接将SSD盘升级为ESSD磁盘,提升IO性能。

      RDS PostgreSQL V11支持DDL回收站、防火墙、增量订阅

      RDS PostgreSQL V11开放事件触发器, 支持DDL回收站、防火墙、增量订阅等功能. 增强安全、增量复制功能.

      Redis 6.0 发布

      Redis 6.0版本在一系列关键领域进行了改进,支持新的Redis协议:RESP3,采用网络请求多线程处理提高了性能,并修复了之前版本的多个缺陷。 此次在阿里云上线的规格是社区云盘版,后续会逐步支持本地盘版。

      MongoDB 支持多安全组设置

      MongoDB 支持多安全组设置,方便客户进行灵活配置

      支持HBase、RDS或其它数据源数据导入HBase Serverless服务

      支持从HBase、RDS、MongoDB,ElasticSearch,TableStore等数据源导入数据到HBase Serverless服务

      阿里云数据库Cassandra版国际站商用发布

      阿里云数据库Cassandra版国际站商用发布:青岛、北京、张家口、呼和浩特、杭州、上海、深圳、香港、新加坡、雅加达、孟买、法兰克福、弗吉尼亚、硅谷、伦敦等地域均已开服。

      数据库备份DBS推出Oracle RAC备份功能

      数据库备份DBS推出Oracle RAC备份功能,支持10g、11g、12c、18c、19c,提供10分钟RPO备份、任意时间点恢复能力。DBS采用Oracle原生RMAN接口,备份数据直接写到云存储,不在本地磁盘中转,数据传输和存储全程加密,数据压缩后长期归档。之前DBS备份已支持Oracle SingleInstance、DataGuard架构。

      数据管理DMS国际站上线新版售卖变更

      数据管理DMS新版统一产品入口,免费版升级为新版的自由操作管控模式,高级版升级为新版的稳定变更管控模式,企业版升级为新版的安全协同管控模式,同时针对安全协同管控模式最高定价降幅68%;优化多个功能模块的国际化显示与交互支持。

      数据管理DMS支持云数据库ClickHouse的统一管理

      数据管理DMS支持ClickHouse的数据库设计及管理,包括实例录入、权限管理、敏感字段管理、数据查询、数据变更等基础管理操作,同时数据工厂/数仓开发-任务编排也已支持ClickHouse。

      数据库自治服务DAS 支持基于真实流量的数据库压测服务

      数据库自治服务 DAS 发布了智能压测,该功能为客户提供轻量级、基于真实的业务流量的数据库压测服务,具备低负载获取源端真实业务流量、支持事务、支持写入流量的压测、支持自定义压测速率等优势,客户可以通过该功能实现上云搬站的容量评估、大促前的数据库压测、异构数据库的迁移评估等。

      数据传输服务DTS支持了TIDB不停机迁移和和PolarDB-X为源的同步功能

      数据传输服务DTS支持将TIDB的结构、全量数据、增量数据迁移到MySQL、PolarDB MySQL,实现TIDB的在线迁移。支持将PolarDB-X到PolarDB-X的全量数据、增量数据同步,支持PolarDB-X到AnalyticDB MySQL的结构、全量数据、增量数据的同步。

      数据传输服务DTS集成云企业网CEN,简化线下IDC自建数据库上云迁移的网络配置

      数据传输服务DTS支持通过云企业网CEN配置迁移和同步任务,DTS可以和CEN里的VPC、VBR、CCN进行互联互通,进而实现DTS和线下数据库的互联互通,大大减少了用户专线接入的网络配置,提升线下IDC数据库通过专线的上云迁移速率。

      三、前沿技术

      智能数据库模型训练时间缩短到最新工作的1/100

      智能数据库基于混合数据模型学习数据联合分布,在几乎不损失精度的情况下,模型训练时间缩短到最新工作(Berkeley发表的VLDB20论文)的1/100,该模型可以作为数据库的基础组件用到优化器以及数据库生成等重要业务。

      全加密数据库MySQL版完成重要生态工具的兼容适配

      包括:TDDL支持密文数据自动加解密功能;DTS支持明文库与密文库间的全量/增量数据同步、全量数据正确性校验功能。上述工具将帮助已有业务平滑、安全地迁入加密数据库环境中。

      空天数据库时空拼图大查询性能优化突破阶段性目标

      采用区别于传统的影像单元化存储策略,借助SQL+NoSQL 协同数据访问以及分布式大查询并行计算优化,将覆盖中国600+遥感影像(总量600G)的时空动态拼图植被指数计算效率从分钟级优化到秒级。

      数据库自治服务DAS用户突破4万

      DAS作为业界第一个数据库自动驾驶云平台,发布以来,用户数已经超过4w,商业化两个月时间付费实例数超过1000,当前日GAAP超过1.7w,月增长率67%。DAS已经为3000+用户,超过1w的实例提供辅助自治服务,用友、米连科技、宝宝树、单创等GC7/6大客户利用DAS的智能压测功能,在上云搬站、大促容量评估、数据库引擎选型切换上面都提供重要的帮助。从数据库冲击Gartner Leader象限调研可以看到,增加了对数据库Autonomous能力的评估,DAS提供的能力可以帮助团队在自治能力上拿到分数。

      四、客户案例

      1、大型药企朗致集团抛弃传统商业数据库迁移上云,数据库运维成本下降50%

      日前,阿里云携手用友帮助国内大型医药控股型集团公司朗致集团将核心ERP系统和核心数据库迁移上云,数据库直接成本下降20%,运维成本下降50%,同时提升了内外部协同效率,促进业务进一步的发展。

      2020 年 1月,朗致集团正式启动上云进程。在用友与阿里云的共同推进下,历时3个月,成功将业务系统及数据库搬迁上云。

      由于采用了阿里云服务,朗致集团大幅降低了机房管理和运维成本;通过业务系统上云,实现企业内外部高效协同,快速响应市场需求;此外,通过实现业务系统的在线化,为公司未来的数字化、智能化战略落地奠定了坚实基础。

      朗致集团IT负责人表示:借助阿里云强大的云数据库运管能力,公司除了大幅度降低运维负担,更重要的是确保了数据安全——RDS秒级历史数据恢复能力,让企业免去了数据丢失的隐忧。

      朗朗.png

      查看详情:https://developer.aliyun.com/article/762664

      2、银泰百货抛弃传统数据库转投阿里云PolarDB,投入产出比增长2倍以上

      5月15日,银泰百货数据库团队举办数据库上云一周年庆祝活动。银泰数据库负责人李亚博透露,公司核心业务系统中的数据库搬迁至阿里云PolarDB之后,在相同成本的情况下可以支撑三倍以上的业务量。

      新零售四年,银泰百货通过数字化转型,成为一家全面架构在云上的互联网百货公司。在此过程中,银泰百货数据库上云对业务飞跃式发展进行了很好地支撑。

      “对于银泰这样的新零售企业,每年有较多的大促节点,大促时数据库资源消耗非常大,对稳定性要求也非常高。” 李亚博表示,数据库上云之后,银泰大促时可以非常灵活地使用PolarDB云数据库的弹性扩容与缩容能力。今天银泰的交易峰值增长20倍以上,但IT成本并没有上涨。

      银泰~~.png

      查看详情:https://developer.aliyun.com/article/760663?spm=a2c6h.13148508.0.0.1f984f0epNgUPJ

      ]]>
      Greenplum json类型的使用 Fri, 20 Jun 2025 02:20:33 +0800 1.建json类型字段的表

      CREATE TABLE orders (
          ID serial NOT NULL PRIMARY KEY,   
          info json NOT NULL
      );
      

      2.插入json类型的数据

      INSERT INTO orders (info) VALUES  
      ( '{ "customer": "John Doe", "items": {"product": "Beer","qty": 6}}' );
      
      INSERT INTO orders (info) VALUES   
      ( '{ "customer": "Lily Bush", "items": {"product": "Diaper","qty": 24}}' ), 
      ( '{ "customer": "Josh William", "items": {"product": "Toy Car","qty": 1}}' ), 
      ( '{ "customer": "Mary Clark", "items": {"product": "Toy Train","qty": 2}}' );
      

      3.查看json类型数据

      select info from orders;

      显示如下:

      111.jpeg

      select info-> 'customer' AS customer from orders;

      显示如下:

      222.jpeg

      select info->> 'customer' AS customer from orders;

      显示如下:

      333.jpeg

      - The operator -> returns JSON object field by key.
      - The operator ->> returns JSON object field by text.
      - The operator -> returns a JSON object, you can chain it with the operator ->> to retrieve a specific node. For example, the following statement returns all products sold:
      SELECT   info -> 'items' ->> 'product' as productFROM   ordersORDER BY product;

      显示如下:

      444.jpeg
      First info -> 'items' returns items as JSON objects. And then info->'items'->>'product' returns all products as text.

      4.在where条件里面使用json

      
      SELECT info ->> 'customer' AS customer,  info -> 'items' ->> 'product' AS product
        FROM orders
       WHERE CAST ( info -> 'items' ->> 'qty' AS INTEGER ) = 2;

      显示如下:

      555.jpeg

      5.在函数里面使用json

      SELECT MIN ( CAST ( info -> 'items' ->> 'qty' AS INTEGER ) ),
             MAX ( CAST ( info -> 'items' ->> 'qty' AS INTEGER ) ),
             SUM ( CAST ( info -> 'items' ->> 'qty' AS INTEGER ) ),
             AVG ( CAST ( info -> 'items' ->> 'qty' AS INTEGER ) ) 
        FROM orders;

      显示如下:

      666.jpeg

      6.json类型的一些函数

      json_each function
      The json_each() function allows us to expand the outermost JSON object into a set of key-value pairs. See the following statement:

      SELECT   json_each (info)FROM   orders;

      显示如下:

      777.jpeg

      json_object_keys function
      To get a set of keys in the outermost JSON object, you use the json_object_keys() function. The following query returns all keys of the nested items object in the info column

      SELECT json_object_keys (info->'items') FROM orders;

      显示如下:

      888.jpeg

      json_typeof function
      The json_typeof() function returns type of the outermost JSON value as a string. It can be number, boolean, null, object, array, and string.
      The following query return the data type of the items:

      SELECT json_typeof (info->'items') FROM orders;

      显示如下:

      999.jpeg

      The following query returns the data type of the qty field of the nested items JSON object.

      SELECT json_typeof ( info->'items'->'qty') FROM orders;

      显示如下:

      111.jpeg

      ]]>
      阿里达摩院:1 秒替换直播背景,像素级视频分割如何实现? Fri, 20 Jun 2025 02:20:33 +0800

      --------点击屏幕右侧或者屏幕底部“+订阅”,关注我,随时分享机器智能最新行业动态及技术干货----------

      1.gif

      与图像识别不同,AI 分析理解视频的技术门槛较高。长期以来,业界在视频 AI 技术的研究上鲜有重大突破。以 CVPR 会议难度最高的比赛之一 DAVIS( Densely Annotated Video Segmentation)为例,该比赛需要参赛团队精准处理复杂视频中物体快速运动、外观变化、遮挡等信息,过去几年,全球顶级科技在该比赛中的成绩从未突破 80 分,而达摩院的模型最终在 test-challenge 上取得了 84.1 的成绩。

      DAVIS 的数据集经过精心挑选和标注,视频分割中比较难的点都有体现,比如:快速运动、遮挡、消失与重现、形变等。DAVIS 的数据分为 train(60 个视频序列), val(30 个视频序列),test-dev(30 个视频序列),test-challenge(30 个视频序列)。 其中 train 和 val 是可以下载的,且提供了每一帧的标注信息。对于半监督任务, test-dev 和 test-challenge,每一帧的 RGB 图片可以下载,且第一帧的标注信息也提供了。算法需要根据第一帧的标注 mask,来对后续帧进行分割。分割本身是 instance 级别的。

      阿里达摩院:像素级视频分割

      阿里达摩院提供了一种全新的空间约束方法,打破了传统 STM 方法缺乏时序性的瓶颈,可以让系统基于视频前一帧的画面预测目标物体下一帧的位置;此外,阿里还引入了语义分割中的精细化分割微调模块,大幅提高了分割的精细程度。最终,精准识别动态目标的轮廓边界,并且与背景进行分离,实现像素级目标分割。

      基本框架

      达摩院的算法基于去年 CVPR 的 STM 做了进一步改进。STM 的主要思想在于,对于历史帧,每一帧都编码为 key-value 形式的 feature。预测当前帧的时候,以当前帧的 key 去和历史帧的 key 做匹配。匹配的方式是 non-local 的。这种 non-local 的匹配,可以看做将当前 key,每个坐标上的 C 维特征,和历史每一帧在这个坐标上的 C 维特征做匹配。 匹配得到的结果,作为一个 soft 的 index,去读取历史 value 的信息。读取的特征和当前帧的 value 拼接起来,用于后续的预测。

      image.png

      image.png

      三大技术创新

      image.png

      1. 空间约束

      STM 的特征匹配方式,提供了一种空间上的长依赖, 类似于 Transformer 中,通过 self-attention 来做序列关联。这种机制,能够很好地处理物体运动、外观变化、遮挡等。但也有一个问题,就是缺乏时序性,缺少短时依赖。当某一帧突然出现和目标相似的物体时,容易产生误召回。在视频场景中,很多情况下,当前帧临近的几帧,对当前帧的影响要大于更早的帧。基于这一点,达摩院提出依靠前一帧结果,计算 attention 来约束当前帧目标预测的位置,相当于对短期依赖的建模。

      具体的方法如下图所示:

      1. 当前帧的特征和前一帧的预测 mask 在 channel 维度上做 concat,得到 HxWx(c+1) 的特征;
      2. 通过卷积将特征压缩为 HxW;
      3. 用 sigmoid 函数将 HxW 的特征,压缩范围,作为空间 attention;
      4. 把 attention 乘到原特征上,作为空间约束。

      image.png

      下图为空间 attention 的可视化结果,可以看到大致对应了前景的位置。

      image.png

      2. 增强 decoder

      达摩院引入了语义分割中的感受野增强技术 ASPP 和精细化分割的微调(refinement)模块。ASPP 作用于 memory 读取后的特征,用于融合不同感受野的信息,提升对不同尺度物体的处理能力。

      image.png

      3. 训练策略

      达摩院提出了一个简单但是有效的训练策略,减少了训练阶段和测试阶段存在的差异,提升了最终效果。

      原始 STM 训练时,会随机从视频中采样 3 帧。这三帧之间的跳帧间隔,随着训练逐渐增大,目的是增强模型鲁棒性。但达摩院发现,这样会导致训练时和测试时不一致,因为测试时,是逐帧处理的。为此,在训练的最后阶段,达摩院将跳帧间隔重新减小,以保证和测试时一致。

      其他

      backbone: 达摩院使用了 ResNeST 这个比较新的 backbone,它可以无痛替换掉原 STM 的 resnet。在结果上有比较明显提升。

      测试策略: 达摩院使用了多尺度测试和 model ensemble。不同尺度和不同 model 的结果,在最终预测的 map 上,做了简单的等权重平均。

      显存优化: 达摩院做了一些显存优化方面的工作,使得 STM 在多目标模式下,可以支持大尺度的训练、测试,以及支持较大的 memory 容量。

      数据: 训练数据上,达摩院使用了 DAVIS、Youtube-VOS,以及 STM 原文用到的静态图像数据库。没有其他数据。

      结果

      达摩院的模型,最终在 test-challenge 上取得了 84.1 的成绩。

      image.png

      在 test-dev 上的消融实验。达摩院复现的 STM 达到了和原文一致的结果。在各种 trick 的加持下, 得到了 11 个点的提升。

      image.png

      随着互联网技术、5G 技术等的发展,短视频、视频会议、直播的场景越来越多,视频分割技术也将成为不可或缺的一环。比如,在视频会议中,视频分割可以精确区分前背景,从而对背景进行虚化或替换;在直播中,用户只需要站在绿幕前,算法就实时替换背景,实现一秒钟换新直播间; 在视频编辑领域,可以辅助进行后期制作。

      image.png

      文章来源:https://www.infoq.cn/article/QyZjDa0A1ePkrog2p1jO

      ]]>
      预警|Apache Dubbo漏洞补丁绕过,阿里云上可默认拦截,请尽快修复 Fri, 20 Jun 2025 02:20:33 +0800 2020年6月29日,阿里云应急响应中心监测到 CVE-2020-1948 Apache Dubbo反序列化漏洞补丁存在缺陷,可以绕过原有补丁并执行任意指令。

      针对这一情况,阿里云默认防御已启动紧急响应,为阿里云上受影响客户免费提供一个月的虚拟补丁帮助缓解该漏洞的影响,建议利用这个期间尽快完成漏洞修复,避免该漏洞被利用造成数据、资产的损失。

      时间线

      • 2020.06.23 监测到 CVE-2020-1948 Apache Dubbo反序列化漏洞
      • 2020.06.29 监测到 CVE-2020-1948 Apache Dubbo反序列化漏洞 补丁绕过

      漏洞描述

      Apache Dubbo是一种基于Java的高性能RPC框架。Apache Dubbo官方披露在Dubbo Provider中存在一个反序列化远程代码执行漏洞(CVE-2020-1948),攻击者可以构造并发送带有恶意参数负载的RPC请求,当恶意参数被反序列化时将导致远程代码执行。

      目前,2.7.7版本的官方补丁中存在缺陷,攻击者可以绕过并执行任意指令,危害极大。

      漏洞影响范围

      • Apache Dubbo 2.7.0~2.7.7
      • Apache Dubbo 2.6.0~2.6.7
      • Apache Dubbo 2.5.x系列所有版本(官方不再提供支持)

      安全建议

      目前官方还未发布针对此漏洞绕过手法的补丁,在阿里云提供一个月的默认防御期限内,建议客户参考以下方法进行缓解,并关注官方补丁动态,及时进行更新:

      1. 升级至2.7.7版本,并根据https://github.com/apache/dubbo/pull/6374/commits/8fcdca112744d2cb98b349225a4aab365af563de的方法进行参数校验
      2. 禁止将Dubbo服务端端口开放给公网,或仅仅只对能够连接至Dubbo服务端的可信消费端IP开放
      3. Dubbo协议默认采用Hessian作为序列化反序列化方式,该反序列化方式存在反序列化漏洞。在不影响业务的情况下,建议更换协议以及反序列化方式。具体更换方法可参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-protocol.html

      安全产品建议

      建议使用阿里云安全的下一代云防火墙产品,即使在官方没有发布针对此漏洞绕过手法的补丁情况下,其阻断恶意外联、配置智能策略的功能,也能够从根本上阻止防御入侵。

      ]]>
      【0629 - 0703 直播导视 | PPT 下载】阿里云Offer 5000人! 快来和阿里妹做同事吧预热来啦! Fri, 20 Jun 2025 02:20:33 +0800 *本预告时间仅供参考,最终直播时间以直播间信息为准。
      *本文提供直播PPT下载,请在对应直播介绍处查看。

      本周直播重磅推荐:

      6月30日:

      研发效能提升的挑战、方法和解决方案

      直播时间:06-30 16:00
      直播亮点:
      我们将引来全新《研发效能36计》的开篇。本次课程将带你一起,分析研发效能提升的挑战,了解其中的关键实践,并设计效能提升的完整解决方案。通过本次课程中,你将了解到:
      云原生和中台化时代研发效能提升的挑战和机会
      如何凝聚组织对研发效能提升的共识
      如何分析和找到导致研发效能问题的根因
      如何综合应用协作、工程、技术三个方面实践,设计研发效能提升的解决方案
      分享嘉宾
      何勉,阿里云云研发部门资深技术专家

      *PPT下载待更新

      【Dev Talks第十期】10分钟快速构建云原生数据仓库

      直播时间:06-30 14:00
      直播亮点:
      企业经常会受到数据孤岛未打通,数据无法实现快速共享的困扰,而构建数据仓库也往往非常复杂,所以在面对数字业务化时缺少基本的系统支持,数字化转型困难重重。通过本课程,无需掌握复杂的大数据技术栈,就能够快速的构建起基于云原生的数据仓库,完成数据接入,数据开发,数据服务发布以及数据可视化工作, 来支持大规模高并发实时在线分析服务,让数据价值在线化,从而使敏捷高效的数字化运营变的可能而且简单。
      分享嘉宾
      南仙,阿里云高级技术专家

      *PPT下载待更新

      6月30日:

      【Dev Talks第十一期】基于OAM和Kubernetes快速构建开放Serverless

      直播时间:07-02 14:00
      直播亮点:
      OAM(Open Application Model)是 2019 年阿里和微软联合在社区推出的开放应用模型,一经推出便被众多 Kubernetes 社区的用户采用,解决了许多诸如“YAML 复杂”、“CRD Operator 难以协作”等应用管理难题。而拥有自动弹性伸缩、无限资源池等先进理念的 Serverless 同样也备受关注。事实上 OAM 也是构建 Serverless 平台的最佳实践,通过 OAM Traits的概念,可以让 Serverless 所需要的各项能力包括 BaaS 资源通过统一的方式管理,同时对用户暴露“以应用为中心”的接口,降低用户使用门槛。
      次演讲将为你解开基于 OAM 和 Kubernetes 快速构建开放 Serverless 平台背后的核心技术原理,真正解决云原生应用的管理难题。
      分享嘉宾
      孙建波(天元),阿里云技术专家

      *PPT下载待更新

      Hadoop 小文件/冷文件分析

      直播时间:07-02 19:00
      直播亮点:
      庞大的小文件和冷文件数量会对HDFS的性能产生不利影响,严重时甚至影响业务稳定性,这个主题将介绍对大容量HDFS进行小文件和冷文件分析的方法,并基于分析结果可以采取哪些处理措施。
      分享嘉宾
      郭聪,花名析源,阿里云计算平台事业部技术专家

      *PPT下载待更新

      阿里云Offer 5000人! 快来和阿里妹做同事吧。

      直播时间:07-09 19:00
      直播亮点:
      阿里云开发者社区“Offer 5000”活动正式开启,7月9日 19:00 - 21:30 15位技术大咖在线招人,更有独家资料《阿里云技术面数红宝书》助你斩获Offer,一键投递进阿里。
      分享嘉宾
      15个团队的技术大咖在线直播招人

      ]]>
      大数据的下一站是什么?服务/分析一体化(HSAP) Fri, 20 Jun 2025 02:20:33 +0800 作者:蒋晓伟(量仔) 阿里巴巴研究员
      因为侧重点的不同,传统的数据库可以分为交易型的 OLTP 系统和分析型的 OLAP 系统。随着互联网的发展,数据量出现了指数型的增长,单机的数据库已经不能满足业务的需求。特别是在分析领域,一个查询就可能需要处理很大一部分甚至全量数据,海量数据带来的压力变得尤为迫切。这促成了过去十多年来以 Hadoop 技术开始的大数据革命,解决了海量数据分析的需求。与此同时,数据库领域也出现了一批分布式数据库产品来应对 OLTP 场景数据量的增长。
      01.jpg
      为了对 OLTP 系统里的数据进行分析,标准的做法是把里面的数据定期(比如说每天)同步到一个 OLAP 系统中。这种架构通过两套系统保证了分析型查询不会影响线上的交易。但是定期同步导致了分析的结果并不是基于最新数据,这种延迟让我们失去了做出更及时的商业决策的机会。为了解决这个问题,近几年出现了 HTAP 的架构,这种架构允许我们对 OLTP 数据库里的数据直接进行分析,从而保证了分析的时效性。分析不再是传统的 OLAP 系统或者大数据系统特有的能力,一个很自然的问题是:既然 HTAP 有了分析的能力,它是不是将取代大数据系统呢?大数据的下一站是什么?

      背景

      为了回答这个问题,我们以推荐系统为例分析一下大数据系统的典型场景。
      当你看到购物应用给你展示正好想要买的商品,短视频应用播放你喜欢的音乐时,推荐系统正在发挥它神奇的作用。一个先进的推荐系统,核心目标是根据用户的实时行为做出个性化的推荐,用户和系统的每一次交互都将即时优化下一步的体验。为了支持这样一个系统,后端的大数据技术栈已经演变为一个非常复杂和多元化的系统。
      下图展示了一个支持实时推荐系统的大数据技术栈。
      02.jpg
      为了提供优质的实时个性化推荐,推荐系统重度依赖实时特征和模型的连续更新。

      实时特征可以分为两类:

      1. 系统会收集大量的用户行为事件(比如说浏览、点击等),以及交易记录(比如说从 OLTP 数据库同步过来的付款记录等)。这些数据量非常巨大(可能高达每秒种数千万甚至上亿条),并且其中的绝大部分不是来自交易系统。为了方便以后使用,这些数据会导入到系统里(图中的 a),同时它们会和各种维表数据做关联推导出一系列重要的特征(图中的 1),这些特征会实时更新到推荐系统以优化用户体验。这里的实时维表关联需要低延迟高吞吐的点查支持才能跟得上新产生的数据。
      2. 系统也会使用滑动窗口等方式去计算出各种不同维度和时间粒度的特征(比如说一个商品过去 5 分钟的点击数、过去 7 天的浏览量和过去 30 天的销售等)。根据滑动窗口的粒度,这些聚合可能通过流计算或者批处理的方式完成。

      这些数据也被用来产生实时和离线机器学习的样本,训练出来的模型经过验证后会持续地更新到推荐系统中。

      上述所解释的是一个先进的推荐系统的核心部分,但这只是整个系统的冰山一角。除此之外还需要实时模型监控、验证、分析和调优等一整套体系,这包含:使用实时大屏去查看 A/B 测试的结果(3),使用交互式分析(4)去做 BI 分析,对模型进行细化和调优。除此之外,运营还会使用各种复杂的查询去洞察业务的进展,并且通过圈人圈品等方式进行针对性的营销。

      这个例子展示了一个非常复杂但典型的大数据场景,从数据的实时导入(a),到预聚合(b),从数据服务(1),持续聚合(3),到交互式查询(4),一直到批处理(2)。这类复杂场景对大数据系统有着非常多样化的需求,在构建这些系统的实践中我们看到了两个新的趋势。

      • 实时化:业务需要快速地从刚刚收集到的数据中获得商业洞察。写入的数据需要在秒级甚至亚秒级就可见。冗长的离线 ETL 过程正在变得不可容忍。同时,收集到的数据比从 OLTP 系统同步过来的数据要大得多,用户浏览点击等日志类数据甚至要比它大几个数量级。我们的系统需要有能力在大量实时数据写入的同时提供低延迟的查询能力。
      • 服务 / 分析的融合:传统的 OLAP 系统在业务中往往扮演着比较静态的角色。我们通过分析海量的数据得到业务的洞察(比如说预计算好的视图、模型等),这些获得的知识通过另外一个系统提供在线数据服务。这里的服务和分析是个割裂的过程。与此不同的是,理想的业务决策过程往往是一个持续优化的在线过程。服务的过程会产生大量的新数据,我们需要对这些新数据进行复杂的分析。分析产生的洞察实时反馈到服务创造更大的商业价值。服务和分析正在形成一个闭环。

      现有的解决方案通过一系列产品的组合来解决实时的服务 / 分析融合的需求。比如说,通过 Apache Flink 做数据的实时预聚合,聚合后的数据会存储在类似 Apache Druid 这种提供多维分析的产品中,并且通过 Apache HBase 这类产品来提供数据服务。这种烟囱式开发的模式会不可避免地产生数据孤岛,从而引起不必要的数据重复,各个产品间复杂的数据同步也使数据的一致性和安全性成为挑战。这种复杂度使得应用开发很难快速响应新需求,影响了业务的迭代速度,也给开发和运维都带来了较大的额外开销。
      03.jpgimage.gif
      我们认为实时的服务 / 分析的融合应该通过一个统一的 Hybrid Serving/Analytical Processing(HSAP)系统来实现。
      通过这样一个系统,应用开发不再需要和多个不同的产品打交道,不再需要去学习和接受每个产品的问题和局限,这能够大大简化业务的架构,提升开发和运维效率。这样一个统一的系统能够避免不必要的数据重复从而节约成本。同时这种架构还能够为系统带来秒级甚至亚秒级的实时性,让业务的决策更实时,从而让数据发挥出更大的商业价值。

      分布式 HTAP 系统虽然具有了实时分析的能力,但是并不能解决大数据的问题。

      首先,交易系统同步过来的数据只是实时推荐系统需要处理的一小部分数据,其他绝大部分数据来自日志等非交易系统(用户每次购买前往往有数十个甚至数百个浏览行为),大部分分析是在这些非交易数据上进行的。但 HTAP 系统并没有这部分数据,所以在这些非交易数据上做分析就无从谈起。
      那么是不是可以将这些非交易数据写入 HTAP 系统来进行分析呢?我们来分析一下 HTAP 系统和 HSAP 系统在数据写入模式上的不同。HTAP 系统的基石和优势是支持细粒度的分布式事务,交易型数据往往以很多分布式小事务的方式写入 HTAP 系统。然而来自日志等系统的数据并没有细粒度分布式事务的语意,如果要把这些非交易型数据导入 HTAP 系统势必会带来不必要的开销。
      相比之下, HSAP 系统并没有这种高频率分布式小事务的需求。数据写入 HSAP 系统一般有两种模式:1)海量的单条数据实时写入;2)相对低频的分布式批量数据写入。这就允许 HSAP 系统在设计上做出一系列优化,从而提升性价比,避免把非交易型数据导入 HTAP 系统带来的不必要开销。
      就算我们不在乎这些开销,假设能不计成本把数据都写入 HTAP 系统,是不是就解决问题了呢?答案仍然是否定的。
      支持好 OLTP 的场景是 HTAP 系统的前提条件,为了做到这点,HTAP 系统往往采用了行存的数据格式,而分析型的查询在行存的效率相比于列存有很大的(数量级的)劣势。具备分析的能力和能够高效地分析并不是一回事。为了提供高效分析的能力,HTAP 系统必须把海量的非交易数据复制到列存,但这势必带来不小的成本,不如把少量的交易数据复制到 HSAP 系统成本更低,同时还能更好地避免对线上交易系统产生影响。
      所以,我们认为 HTAP 和 HSAP 会相辅相成,分别引领数据库和大数据领域的方向。

      HSAP 的挑战

      作为一种全新的架构,HSAP 面临着和已有的大数据以及传统的 OLAP 系统相比很不一样的挑战。

      高并发的混合工作负载:HSAP 系统需要处理远远超出传统的 OLAP 系统的并发查询。在实践中,数据服务的并发远远超出了 OLAP 查询。比如说,我们在实践中见到数据服务需要处理高达每秒钟数千万个查询,这比 OLAP 查询的并发高出了 5 个数量级。同时,和 OLAP 查询相比,数据服务型查询对延迟有着更加苛刻的要求。除此之外,更大的挑战是系统在提供数据服务查询的同时需要处理非常复杂的分析型查询。这些混合查询负载在延迟和吞吐间有着非常不同的取舍。如何高效地利用系统资源处理好这些非常不一样的查询,并且保证每个查询的 SLO 是一个巨大的挑战。

      高吞吐实时数据导入:在处理高并发的查询负载的同时,HSAP 系统还需要支持海量数据的实时写入。这些实时写入的数据量远远超出了传统的 OLAP 系统的需求。比如说,上面的实时推荐场景会持续写入每秒钟数千万甚至上亿条事件。和传统的 OLAP 系统的另外一个区别是 HSAP 系统对数据的实时性有着很高的要求,写入的数据需要在秒级甚至亚秒级可见,这样才能保证我们服务和分析结果的时效性。

      弹性和可扩展性:数据写入和查询负载可能会有突发的高峰,这对系统提出了很高的弹性和可扩展性的要求。在实践中,我们注意到数据写入峰值能达到平均的 2.5 倍,查询的峰值能达到平均的 3 倍。而且数据写入和查询的峰值不一定同时出现,这也需要系统有根据不同的峰值做迅速调整的能力。

      HSAP 的系统设计

      为了应对这些挑战,我们认为一个典型的 HSAP 系统可以采用类似于上图的一个架构。
      image.gif04.jpg
      存储计算分离:所有的数据存储在一个分布式文件系统中,我们以数据分片的方式来扩展系统,Storage Manager 会管理这些数据分片(Shard),Resource Manager 管理系统的计算资源,保证系统能够处理高吞吐的数据写入和查询的需求。这种架构能够快速应对工作负载的变化,当查询负载变大需要更多的计算资源的时候可以扩展计算资源,当数据量快速增长的时候可以快速扩展存储资源。存储计算分离的架构保证了不需要等待移动 / 拷贝数据就能快速完成这些操作。这种架构较大地简化了运维,为系统的稳定性提供了保障。

      统一的实时存储:为了能够支持各种查询模式,统一的实时存储层至关重要。查询大体可以分为两类,一类是简单的点查询(数据服务类的大多是这类),另一类是扫描大量数据的复杂查询(分析类的大多是这类),当然这是一个连续变化的过程,很多查询介于两者之间。这两种查询模式对数据存储也提出了不同的要求。行存储能够比较高效地支持点查询,而列存储在支持大量扫描的查询上有明显的优势。我们可以通过类似 PAX 的方式在行存和列存之间做一个折衷,付出的代价是可能在点查和扫描数据的场景都不能获得最佳的性能。我们希望在两种场景都做到最优,所以在系统里同时支持了行存和列存,用户可以根据场景选择每个表的存储方式。对同时有两种需求的表我们通过索引的抽象允许用户同时选择两种存储,系统通过索引维护的机制保证两者间的一致性。在实践中我们发现这种设计带来的效率和灵活性能够更好地支持业务。

      工作负载的隔离:系统在混合工作负载下的 SLO 是通过调度来保证的。在理想情况下,一个大查询就应该能把所有的资源都利用起来。而当有多个查询同时运行的时候,这些查询需要公平地共享资源。由于服务型的查询通常比较简单从而需要的资源比较少,这种公平调度的机制能够保证服务型查询的延迟即使在有复杂的分析型查询的情况下仍然能够得到保障。作为一个分布式的系统,调度可以分为分布式和进程内两部分。Coordinator 会把一个查询分解为多个任务,这些任务被分发到不同的进程,Coordinator 需要采取一定的策略保证公平性。同样重要的是,在一个进程内我们也需要让不同任务公平地分享资源。由于操作系统并不理解任务间的关系,所以我们在每个进程里实现了一个用户态的 Scheduler 去更灵活地支持工作负载的隔离。

      系统的开放性:很多业务已经使用了其他存储平台或者计算引擎,新的系统必须考虑和已有系统的融合。对时效性要求高的查询,计算和存储的一体化能够带来明显的优势。但是对时效性不高的离线计算,存储层可以提供统一的接口开放数据,这种开放性允许其他引擎把数据拉出去处理能够赋予业务更大的灵活度。开放性的另外一面是对存储在其他系统中数据进行处理的能力,这个可以通过联邦查询的方式去实现。

      HSAP 的应用

      这里我们分享一下阿里巴巴搜索推荐精细化运营业务,下图显示了在采用 HSAP 前这样一个系统架构的示例。
      05.jpgimage.gif
      我们通过一系列存储和计算引擎(HBase、Druid、Hive、Drill、Redis 等)的复杂配合才能满足业务的需求,并且多个存储之间需要通过数据同步任务来保持大致的同步。这种业务架构极其复杂,整个业务的开发耗费了大量的时间。
      06.jpg
      我们在 2019 年的双十一使用 HSAP 系统升级了这个业务,新架构得到了极大的简化。用户、商品、商家属性数据和海量的用户行为数据经过实时和离线的数据清洗统一进入 HSAP 系统,由 HSAP 系统向上承接了实时大屏、实时报表、效果跟踪、实时数据应用等查询和分析服务。实时大屏、实时销售预测、实时库存监控、实时 BI 报表实时监测业务进展,洞悉运营增长,跟踪算法效果,从而助力高效决策。实时标签、实时画像、竞对分析、圈人圈品、权益投放等数据产品助力精细化运营和决策。实时数据接口服务支持算法调控、库存监控预警等服务。一套 HSAP 系统实现了全渠道全链路的数据共享和复用,解决了运营、产品、算法、开发、分析师到决策层不同业务视角的数据分析和查询需求。

      总结

      HSAP 架构通过统一的实时存储,数据无需复制就能一站式提供简单查询、OLAP 分析、在线数据服务等多样化的数据查询和应用服务,满足数据应用方的访问和接入需求。这种新架构大大地降低了业务的复杂度,让我们能够快速应对新的业务需求。它提供的秒级甚至亚秒级实时性让决策更及时高效,从而让数据创造出更大的商业价值。
      最后,欢迎加入交互式分析Hologres交流群
      image.png

      ]]>
      错误码如何设计才合理?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 image.png

      一 前言

      在工作中,接触过不少外部接口,其中包括:支付宝,微信支付,微博开发平台,阿里云等等。每家公司错误码风格都不尽相同,有使用纯数字的,有使用纯英文的,也有使用字母和数字组合的。也接触过很多内部系统,错误码设计也不尽相同。

      错误码的输出路径

      面向日志输出

      • 服务内传递,最终是输出到日志。
      • 域内服务间,比如同时大麦电商之间的系统,最终目的是输出到日志。

      面向外部传递

      • 域内向域外
      • 服务端传递到前端
      • OpenAPI 错误码
      • 内部不同域之间

      错误码使用场景

      • 通过错误码配置监控大盘。
      • 通过日志进行问题排查,快速定位问题。
      • 后端服务之间错误码传递。
      • 前端展示的错误提示/OpenAPI。

      本文希望从错误码使用的不同场景讨论得到一个合理的错误码规约,得到一个面向日志错误码标准和一个面向外部传递的错误码标准。

      PS:本文引用全部引自阿里巴巴《Java 开发手册》,下称《手册》。

      二 什么是错误码

      错误码要回答的最根本的问题是,谁的错?错在哪?

      那么一个错误能表示出谁的错和错在哪里就是一个好的错误码吗?答案显然是否定的,这个标准太基础了。

      • 好的错误码必须能够快速知晓错误来源。
      • 好的错误码必须易于记忆和对比。
      • 好的错误码必须能够脱离文档和系统平台达到线下轻量沟通的目的(这个要求比较高)。

      引自《手册》- 异常日志-错误码

      错误码的制定原则:快速溯源、简单易记、沟通标准化。

      说明:错误码想得过于完美和复杂,就像康熙字典中的生僻字一样,用词似乎精准,但是字典不容易随身携带并且简单易懂。
      正例:错误码回答的问题是谁的错?错在哪?
      1)错误码必须能够快速知晓错误来源,可快速判断是谁的问题。
      2)错误码易于记忆和比对(代码中容易 equals)。
      3)错误码能够脱离文档和系统平台达到线下轻量化地自由沟通的目的。

      这个原则写在异常日志-错误码这个章节,我认为同样适用在面向用户的错误码。

      image.png

      三 错误码规范

      错误码定义要有字母也要有数字

      纯数字错误码

      错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。

      说明:数字是一个整体,每位数字的地位和含义是相同的。
      反例:一个五位数字 12345,第1位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地分辨每位数字的不同含义。

      《手册》说明了纯数字错误码存在的问题。

      纯字母错误码

      那么纯字母错误码不香吗?有两个问题:

      • 对于使用汉语的我们用英语去准确描述一个错误有时是比较困难的。
      • 纯英文字母的错误码不利于排序。

      错误码尽量有利于不同文化背景的开发者进行交流与代码协作。

      说明:英文单词形式的错误码不利于非英语母语国家(如阿拉伯语、希伯来语、俄罗斯语等)之间的开发者互相协作。

      快速溯源 | 简单易记 | 沟通标准化

      什么是快速溯源?就是一眼看上去就知道哪里出了什么问题。

      李雷负责 A 服务,韩梅梅负责 B 服务。韩梅梅发现服务 B 出现了一个错误码,韩梅梅能够快速定位这是服务 A 的内部业务异常造成的问题,这个时候韩梅梅就可以拿着错误码找到李雷说,"hi,Li Lei,How old are you。(李雷,怎么老是你)"。李雷拿过来错误码一看,内心万马奔腾,一下就能知道这是上游 Polly 负责的应用阿尔法出了错。

      怎么能达到这个效果呢?

      • 首先要有一套标准并且在域内各个业务都在用同样的标准。
      • 其次要求错误码有自我解释的能力是有信息含量的有意义。
      • 最后在域内要传递错误码。

      错误码标准的意义

      开宗明义借用了《手册》对于错误码定义的原则作为错误码规范能够给我们带来的收益。我想再次强调并且试着从反面阐述没有错误码标准会带来的成本。

      错误码是用来做沟通的:系统与系统间的沟通,人与人间的沟通,人与系统间的沟通。

      试想下面这个场景:

      韩梅梅看到一个异常日志其中一个纯数字的错误码。

      韩梅梅需要理解这串数字代表的是什么,它到底是不是一个错误码,经过几秒钟确定下来这是一个错误码,但她不能确定这是不是本系统中错误码,因为在她负责的系统是由韩梅梅、Lucy 和 Lily 三个人共同维护的,每个人都按照自己的理解定义了一套错误码。

      韩梅梅去系统源码中查找这个错误码,但是发现这个错误码并不是本系统的错误码。

      然后再前翻两页后翻两页从日志上下文中确定这是李雷负责系统的错误码,“Li Lie,how old are you?”。

      韩梅梅把错误码甩到李雷脸上,李雷一脸懵逼,这是我的系统的错误码吗?

      李雷也不确定,因为李雷负责的系统是由李雷、林涛和 Jim 维护的,也是三人共同维护的。

      李雷只好打开源码,还真是!

      上边的场景经过了发现-初判断-判断来源-确定来源-沟通-二次判断-二次确认七个步骤。

      希望上边的场景描述能够说明没有统一标准的错误所带来的成本。

      四 面向日志的错误码

      输出到日志的错误码有两个用途:

      • 用来快速溯源找到问题。
      • 用来形成监控大盘。

      错误码设计

      《手册》对于错误码的建议有非常多的可取参考的地方:

      错误码不体现版本号和错误等级信息。

      说明:错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。

      错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。

      错误码不能直接输出给用户作为提示信息使用。

      说明:堆栈(stack_trace)、错误信息(error_message)、错误码(error_code)、提示信息(user_tip)是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。

      在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B,并且在错误信息上带上原有的第三方错误码。

      结合错误码设计原则、错误码用途、规约建议,面向服务端日志的错误码应该是如下形式。

      错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。

      错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。
      说明:数字是一个整体,每位数字的地位和含义是相同的。

      反例:一个五位数字 12345,第 1 位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地分辨每位数字的不同含义。

      按照《手册》的建议设计出的面向日志的错误码定义共十三位(十位有意义,三位连接符),并且应该具有如下分类:

      • 应用标识,表示错误属于哪个应用,三位数字。
      • 功能域标识,表示错误属于应用中的哪个功能模块,三位数字。
      • 错误类型,表示错误属于那种类型,一位字母。
      • 错误编码,错误类型下的具体错误,三位数字。

      image.png

      《手册》还有一条是规定错误码应该如何定义:

      错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。

      说明:错误产生来源分为 A/B/C,A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付超时等问题;B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;C 表示错误来源于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的步长间距预留 100。

      五位错误码的好处是易记,但是对于面向日志的错误码场景利用错误码制作需要分类的业务监控大盘将变得比较困难,比如统计应用 A 的功能 B 的错误出现次数。

      同样在系统间传递这个类型的错误码非常有可能发生错误码冲突。

      当然对于分为四段的错误码同样尤其不好的一面,应用标识和功能域标识需要有专人去管理或者开发一个错误码管理工具,否则时间一长很容易产生定义的混乱形成破窗。

      《手册》对于错误码定义我认为非常适合面向外部传递的错误码。简单、易记、是大家熟悉的错误码样式,并且透出的错误码数量是非常有限的。

      不用枚举定义错误码

      国际化支持是一个不使用枚举定义错误码很重要的理由。

      我们通过 i18n 的支持可以做到错误码、错误状态、错误描述的管理。

      五 面向外部传递的错误码

      面向外部传递的错误码是为了把域内的错误信息传递出去。

      可以让域外系统通过错误码进行错误码进行后续的动作或是中断操作或是记录日志继续执行。

      可以让前端通过错误码给出用户准确的错误提示或者忽略错误进行重试。

      错误码设计

      根据《手册》给出的错误码定义建议设计出的面向外部传递的错误码共五位,并且有如下分类:

      • 错误类型,表示错误来源,一位字母。
      • 错误编码,表示具体错误,四位数字。

      image.png

      错误码的后三位编号与 HTTP 状态码没有任何关系。

      错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。

      说明:数字是一个整体,每位数字的地位和含义是相同的。
      反例:一个五位数字 12345,第1位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地分辨每位数字的不同含义。

      下图是《手册》给出的错误码示例:

      image.png

      他山之石

      他山之石不一定能攻玉。

      谷歌 API 错误码定义

      谷歌 API 的错误码定义与 HTTP 状态码有着非常强的联系,并且是一个全数字错误码定义。

      没有明显的错误分类,快速识别和自解释能力比较弱。

      image.png

      腾讯 OpenAPI(文智)错误码定义

      这也是一个全数字的错误码,没有明确的分类字段,纯数字的某一位已看不出明显的分类。

      不利于进行感性记忆。
      image.png

      微博 API 错误码定义

      同样是全数字的错误码定义:

      image.png

      其他建议

      《手册》中有一条建议:

      全部正常,但不得不填充错误码时返回五个零:00000。

      这也是在其他家 API 错误码中能够看到的定义。

      参考

      《阿里巴巴java开发手册》
      《Google API Design Guide 》(https://www.bookstack.cn/books/API-design-guide
      《阿里云-文件存储-错误码》(https://help.aliyun.com/document_detail/62603.html
      《微博开放平台-API-错误码》(https://open.weibo.com/wiki/Help/error
      《腾讯开放平台-错误码》(https://wiki.open.qq.com/wiki/%E9%94%99%E8%AF%AF%E7%A0%81

      ]]>
      在Kubernetes中用Alluxio加速Spark数据访问(一)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1.背景信息

      1.1 alluxio

      Alluxio是一个开源的基于内存的分布式存储系统,适合作为云上大数据和AI / ML的数据编排方案。Alluxio可以同时管理多个底层文件系统,将不同的文件系统统一在同一个名称空间下,让上层客户端可以自由访问统一名称空间内的不同路径,不同存储系统的数据。

      alluxio的short-circuit功能可以使alluxio客户端直接访问alluxio worker所在主机的工作存储,而不需要通过网络栈与alluxio worker完成通信,可以提高性能。

      1.2 spark operator

      Spark-operator用于管理k8s集群中spark job。通过spark-operator可以在k8s集群中创建、查看和删除spark job。

      2.前提条件

      本文档的操作依赖如下的一些条件:

      • kubernetes集群:版本大于1.8,本次实验的集群通过阿里云容器服务创建,集群名称为"ack-create-by-openapi-1"。

      image.png

      • 安装有linux或者mac操作系统的计算机作为我们的实验环境(本次实验中,假设该计算机名称为alluxio-test)。该计算机需要准备如下环境:

        • docker >= 17.06
        • kubectl >= 1.8,能够连接kubernets集群ack-create-by-openapi-1

      3.实验步骤

      实验步骤主要包括如下几步:

      • 部署alluxio
      • 部署spark-operator
      • 制作spark docker镜像
      • 上传文件到alluxio
      • 提交spark job

      下面将对每个步骤进行说明:

      3.1 部署alluxio

      进入容器服务应用目录,在右上角的搜索框中搜索"alluxio",然后进入alluxio主界面,如图:
      image.png

      选择“参数”,修改配置中properties部分的"alluxio.user.short.circuit.enabled"值为"false",然后选择将alluxio安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),最后点击创建,如图
      image.png

      点击创建后,使用kubectl给待安装的alluxio组件的节点打上标签"alluxio=true",首先查看该集群有哪些节点:

      $ kubectl get nodes -o wide
      NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
      cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

      可以看到有三个worker节点,分别为:

      • cn-beijing.192.168.8.15
      • cn-beijing.192.168.8.16
      • cn-beijing.192.168.8.17

      我们给是三个节点都打上标签"alluxio=true":

      $ kubectl label nodes cn-beijing.192.168.8.15 
        cn-beijing.192.168.8.16 
        cn-beijing.192.168.8.17 
        alluxio=true

      使用kubectl查看各个pod是否都处于running状态:

      $ kubectl get po -n alluxio
      NAME                   READY   STATUS    RESTARTS   AGE
      alluxio-master-0       2/2     Running   0          4h1m
      alluxio-worker-5zg26   2/2     Running   0          4h1m
      alluxio-worker-ckmr9   2/2     Running   0          4h1m
      alluxio-worker-dvgvd   2/2     Running   0          4h1m

      验证alluxio是否处于ready:

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fsadmin report capacity
      
      Capacity information for all workers:
          Total Capacity: 3072.00MB
              Tier: MEM  Size: 3072.00MB
          Used Capacity: 0B
              Tier: MEM  Size: 0B
          Used Percentage: 0%
          Free Percentage: 100%
      
      Worker Name      Last Heartbeat   Storage       MEM
      192.168.8.15    0                capacity      1024.00MB
                                        used          0B (0%)
      192.168.8.16    0                capacity      1024.00MB
                                        used          0B (0%)
      192.168.8.17    0                capacity      1024.00MB
                                        used          0B (0%)

      3.2 部署spark-operator

      进入容器服务应用目录,在右上角的搜索框中搜索"ack-spark-operator",然后进入ack-spark-operator主界面,如图:
      image.png
      选择将ack-spark-operator安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),然后点击创建,如图:
      image.png

      本次实验将会使用sparkctl向k8s集群提交一个spark job,需要将sparkctl安装到我们在"2.前提条件"中所提到的实验环境"alluxio-test"中:

      $ wget http://spark-on-k8s.oss-cn-beijing.aliyuncs.com/sparkctl/sparkctl-linux-amd64 -O /usr/local/bin/sparkctl
      $ chmod +x /usr/local/bin/sparkctl

      3.3 制作spark docker镜像

      spark下载页面下载所需的spark版本,本次实验选择的saprk版本为2.4.6。运行如下命令下载spark:

      $ cd /root
      $ wget https://mirror.bit.edu.cn/apache/spark/spark-2.4.6/spark-2.4.6-bin-hadoop2.7.tgz
      #

      下载完成后,执行解压操作:

      $ tar -xf spark-2.4.6-bin-hadoop2.7.tgz
      $ export SPARK_HOME=/root/spark-2.4.6-bin-hadoop2.7

      spark docker镜像是我们提交spark任务时使用到的镜像,这个镜像中需要包含alluxio client jar包。使用如下的命令获取alluxio client jar包:

      $ id=$(docker create alluxio/alluxio-enterprise:2.2.1-1.4)
      $ docker cp $id:/opt/alluxio/client/alluxio-enterprise-2.2.1-1.4-client.jar 
          $SPARK_HOME/jars/alluxio-enterprise-2.2.1-1.4-client.jar
      $ docker rm -v $id 1>/dev/null

      alluxio client jar包准备好以后,开始构建镜像:

      $ docker build -t spark-alluxio:2.4.6 -f kubernetes/dockerfiles/spark/Dockerfile $SPARK_HOME

      请记住镜像名称“spark-alluxio:2.4.6”,在向k8s提交spark job中会用到这个信息。

      镜像构建完成以后,对镜像的处理有两种方式:

      • 如果有私有镜像仓库,将该镜像推送到私有镜像仓库中,同时保证k8s集群节点能够pull该镜像
      • 如果没有私有镜像仓库,那么需要使用docker save命令将该镜像导出,然后scp到k8s集群的各个节点,在每个节点上使用docker load命令将镜像导入,这样就能保证每个节点上都存在该镜像。

      3.4 上传文件到alluxio

      文章开头提到过:本次实验是提交一个spark job到k8s中,该spark job的目标是对某一个文件统计每一个单词出现的次数。现在需要把这个文件传到alluxio存储上,这里为了方便,直接把alluxio master中/opt/alluxio-2.3.0-SNAPSHOT/LICENSE(文件路径可能因alluxio版本有点差异)这个文件传到alluxio上。

      使用"kubectl exec"进入alluxio master pod,并拷贝当前目录下的LICENSE文件到alluxio的根目录中:

      $ kubectl exec -ti alluxio-master-0  -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fs copyFromLocal LICENSE /

      接着查看一下LICENSE这个文件分成的block被alluxio放到哪些worker上了。

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      
      bash-4.4# alluxio fs stat /LICENSE
      /LICENSE is a file path.
      FileInfo{fileId=33554431, fileIdentifier=null, name=LICENSE, path=/LICENSE, ufsPath=/opt/alluxio-2.3.0-SNAPSHOT/underFSStorage/LICENSE, length=27040, blockSizeBytes=67108864, creationTimeMs=1592381889733, completed=true, folder=false, pinned=false, pinnedlocation=[], cacheable=true, persisted=false, blockIds=[16777216], inMemoryPercentage=100, lastModificationTimesMs=1592381890390, ttl=-1, lastAccessTimesMs=1592381890390, ttlAction=DELETE, owner=root, group=root, mode=420, persistenceState=TO_BE_PERSISTED, mountPoint=false, replicationMax=-1, replicationMin=0, fileBlockInfos=[FileBlockInfo{blockInfo=BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}, offset=0, ufsLocations=[]}], mountId=1, inAlluxioPercentage=100, ufsFingerprint=, acl=user::rw-,group::r--,other::r--, defaultAcl=}
      Containing the following blocks:
      BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}

      可以看到LICENSE这个文件只有一个block(id为16777216),被放在了ip为192.168.8.17的k8s节点上。我们使用kubectl查看该节点名称为cn-beijing.192.168.8.17

      $ kubectl get nodes -o wide
      NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
      cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

      3.5 提交spark job

      下面的步骤将提交一个spark job到k8s集群中,该job主要是计算alluxio中/LICENSE文件的每个单词出现的次数。

      在步骤3.4中我们获取到LICENSE这个文件所包含的block都在节点cn-beijing.192.168.8.17上,此次实验中,我们通过指定node selector让spark driver和spark executor都运行在节点cn-beijing.192.168.8.17,验证在关闭alluxio的short-circuit功能的情况下,spark executor和alluxio worker之间的通信是否通过网络栈完成。

      • 说明:如果在开启alluxio的short-circuit功能的情况下,并且spark executor与其所要访问的文件(本次实验为/LICENSE这个文件)的block在同一个k8s节点上,那么spark executor中的alluxio client与该k8s节点上的alluxio worker之间的通信通过domain socket方式完成。

      首先生成提交spark job的yaml文件:

      $ export SPARK_ALLUXIO_IMAGE=<步骤3.3中制作的image,即spark-alluxio:2.4.6>
      $ export ALLUXIO_MASTER="alluxio-master-0"
      $ export TARGET_NODE=<步骤3.4获取到的LICENSE文件的block存储的节点,即cn-beijing.192.168.8.17>
      $ cat > /tmp/spark-example.yaml <<- EOF
      apiVersion: "sparkoperator.k8s.io/v1beta2"
      kind: SparkApplication
      metadata:
        name: spark-count-words
        namespace: default
      spec:
        type: Scala
        mode: cluster
        image: "$SPARK_ALLUXIO_IMAGE"
        imagePullPolicy: Always
        mainClass: org.apache.spark.examples.JavaWordCount
        mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.6.jar"
        arguments:
          - alluxio://${ALLUXIO_MASTER}.alluxio:19998/LICENSE
        sparkVersion: "2.4.5"
        restartPolicy:
          type: Never
        volumes:
          - name: "test-volume"
            hostPath:
              path: "/tmp"
              type: Directory
        driver:
          cores: 1
          coreLimit: "1200m"
          memory: "512m"
          labels:
            version: 2.4.5
          serviceAccount: spark
          volumeMounts:
            - name: "test-volume"
              mountPath: "/tmp"
          nodeSelector:
            kubernetes.io/hostname: "$TARGET_NODE"
        executor:
          cores: 1
          instances: 1
          memory: "512m"
          labels:
            version: 2.4.5
          nodeSelector:
            kubernetes.io/hostname: "$TARGET_NODE"
          volumeMounts:
            - name: "test-volume"
              mountPath: "/tmp"
      EOF

      然后,使用sparkctl提交spark job:

      $ sparkctl create /tmp/spark-example.yaml

      4.实验结果

      当提交任务后,使用kubectl查看spark driver的日志:

      $ kubectl get po -l spark-role=driver
      NAME                                 READY   STATUS      RESTARTS   AGE
      spark-alluxio-1592296972094-driver   0/1     Completed   0          4h33m
      
      $ kubectl logs spark-alluxio-1592296972094-driver --tail 20
      
      USE,: 3
      Patents: 2
      d): 1
      comment: 1
      executed: 1
      replaced: 1
      mechanical: 1
      20/06/16 13:14:28 INFO SparkUI: Stopped Spark web UI at http://spark-alluxio-1592313250782-driver-svc.default.svc:4040
      20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend: Shutting down all executors
      20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint: Asking each executor to shut down
      20/06/16 13:14:28 WARN ExecutorPodsWatchSnapshotSource: Kubernetes client has been closed (this is expected if the application is shutting down.)
      20/06/16 13:14:28 INFO MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped!
      20/06/16 13:14:28 INFO MemoryStore: MemoryStore cleared
      20/06/16 13:14:28 INFO BlockManager: BlockManager stopped
      20/06/16 13:14:28 INFO BlockManagerMaster: BlockManagerMaster stopped
      20/06/16 13:14:28 INFO OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped!
      20/06/16 13:14:28 INFO SparkContext: Successfully stopped SparkContext
      20/06/16 13:14:28 INFO ShutdownHookManager: Shutdown hook called
      20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /var/data/spark-2f619243-59b2-4258-ba5e-69b8491123a6/spark-3d70294a-291a-423a-b034-8fc779244f40
      20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /tmp/spark-054883b4-15d3-43ee-94c3-5810a8a6cdc7

      最后我们登陆到alluxio master上,查看相关指标统计到的值:

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fsadmin report metrics
      Cluster.BytesReadAlluxio  (Type: COUNTER, Value: 290.47KB)
      Cluster.BytesReadAlluxioThroughput  (Type: GAUGE, Value: 22.34KB/MIN)
      Cluster.BytesReadDomain  (Type: COUNTER, Value: 0B)
      Cluster.BytesReadDomainThroughput  (Type: GAUGE, Value: 0B/MIN)

      BytesReadAlluxio和BytesReadAlluxioThroughput代表数据从网络栈传输;BytesReadDomain和BytesReadDomainThroughput代表数据从domain socket传输。可以看到所有数据都是从网络栈传输的(即使spark executor和LICENSE文件的block在同一k8s节点上)。

      5.参考文档

      ]]>
      【精品问答】110+数据挖掘面试题集合 | 技术日报(17期)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 每日干货推荐

      年中福利!阿里技术电子书 80 本+上半年大会资料 PDF 免费下载! >>>

      阿里云开发者社区超大技术福利!80+阿里系电子书开放下载,覆盖 Java、物联网、云原生、前端、大数据、开源、AI 等技术领域,深度分享阿里工程师实践精华,顶级技术内容一手掌握。

      更多精彩文章

      编程萌新看过来,一文带你入门Python | 伸手党福利篇>>>

      这是一篇介绍Python入门的文章,对于没有任何编程经验甚至不懂电脑的新手都是非常实用的。本文会从计算机的使用开始讲解,中间搭配一些经典的针对知识点的练习,最终大家都可以用Python开发出一个小游戏,快来跟我一起往下看!

      【【精品问答】110+数据挖掘面试题集合>>>

      数据挖掘工程师面试宝典双手呈上,快来收藏吧!

      精品公开课

      阿里云千万级架构的构建——架构的成长演变之路>>>

      在阿里云我们如何构建千万级架构?本次分享内容摘选《「阿里云」运维架构实践秘籍》,主要介绍了在云端如何从单机演变到如今普及的分布式+大数据架构,并且又如何演变到微服务+Fast Data千万级架构。

      ]]>
      阿里云省钱方法5条-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 以最省钱的方式购买阿里云服务器等云产品,要学会合理用户资格、代金券优惠和促销活动,才是最省钱的方法,利用好下面这5种方法,可以有效的节约我们购买阿里云产品的成本。

      阿里云省钱方法第1条:阿里云代金券一定要领取

      阿里云代金券是阿里云官方的一种长期行活动,旨在让用户利用代金券减少购买阿里云服务器等产品的成本,代金券有云服务器专用代金券、云数据库产品专用代金券、云产品通用代金券,其中云服务器专用代金券和云产品通用代金券是我们在购买阿里云服务器的时候可以使用的,使用方法的是最后支付购买订单的时候,系统会自动根据订单金额匹配可使用的代金券面额。
      阿里云代金券地址:阿里云官方云小站
      代金券抵扣最新版.png

      阿里云省钱方法第2条:尽量使用新用户账号购买

      阿里云针对新用户的优惠力度是最大的,无论是注册域名还是购买阿里云虚拟主机、ecs云服务器等热门产品,新用户的优惠力度都是最大的,例如1核1GB基础版的阿里云独享云虚拟主机,新用户购买只要206元,而老用户购买就需要500元了,足足高了294元。
      新用户还有一个好处就是参与阿里云的各种促销活动更加便利,因为阿里云的促销活动大多都只限新用户购买,例如当下最热门的活动价格只要91.80元的共享型s6实例1核2G1M带宽云服务器,就只能新用户购买。
      新用户优惠.png

      阿里云省钱方法第3条:尽量通过阿里云促销活动购买

      阿里云的各种促销活动不仅包含阿里云服务器产品,其他阿里云热门产品,例如云数据库、CDN、防火墙、云安全类产品都是可以通过促销活动购买到的,利用活动购买可以便宜很多,例如用户购买最多的计算型c5实例2核4G配置的阿里云服务器,通过阿里云活动购买价格只要699.84元,比原价购买节省了1892.16元。
      699.png

      阿里云省钱方法第4条:合理利用学生身份

      阿里云针对学生用户是有单独优惠的,如果你的实名认证身份证没有超过24岁,那么你可以直接获得学生身份,有了学生身份可以以9.5元一个月、114元一年的优惠价格购买到一款1核2G配置的ecs云服务器或者轻量应用服务器。
      阿里云学生机申请地址:云翼计划
      学生套餐.png

      阿里云省钱方法第5条:老用户也要学会利用好阿里云各种优惠福利

      虽然阿里云针对老用户的专属活动不多,但是我们应该多关注阿里云各种活动的具体规则,有些优惠政策老用户也是可以参与的,例如618年中大促活动中的云服务器专场,就有如下优惠政策是老用户也可以参与的:

      • 续费权益规则:活动期间,领取到续费权益的用户,可享受单实例续费,包1年7折优惠。
      • 购物车满减权益规则:活动期间,领取到购物车满减权益的用户,可在页面选择产品加入购物车后享受。
        618权益.png

      以上就是5条当下比较有效的阿里云省钱方法,相信利用好这些方法,可以帮助我们以最低的价格购买到自己所需的阿里云产品。

      ]]>
      浅谈动图文件格式 - GIF-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      简介

      GIF的全称是Graphics Interchange Format,可译为图形交换格式,用于以超文本标志语言(Hypertext Markup Language)方式显示索引彩色图像,在因特网和其他在线服务系统上得到广泛应用。GIF虽然是一个古老的文件格式,但是随着移动互联网的发展,在手机社交应用中因表情包和动图的火爆而重新大范围的进入公众视野, 重新流行起来。

      版本

      GIF具有GIF87a和GIF89a两个版本。
      GIF87a版本是1987年推出的,一个文件存储一个图像,严格不支持透明像素;GIF87a采用LZW压缩算法,它能够在保持图像质量的前提下将图像尺寸压缩百分之二十到二十五。
      GIF89a版本是1989年推出的很有特色的版本,该版本允许一个文件存储多个图像,可实现动画功能,允许某些像素透明。在这个版本中,为GIF文档扩充了图形控制区块、备注、说明、应用程序编程接口4个区块,并提供了对透明色和多帧动画的支持。

      文件格式详解

      一个典型的GIF文件,由以下内容构成:

      • Q:这是什么文件?A:GIF (Header)
      • Q:显示它需要多大的区域?A:宽xxx,高xxx(LSD)
      • Q:显示它需要准备哪些色彩?A:色表(LSD、GCT、ID、LCT)
      • Q:某一帧的内容是什么样的?A:LZW压缩后的像素列表
      • Q:如何绘制?A:位置、尺寸、透明色、隔行扫描(GCE、ID)
      • Q:绘制完一帧后还要做什么?A:Disposal Method(GCE)
      • Q:开始绘制下一帧前等待多久?A:0.01秒的倍数(GCE)
      • Q:循环播放几次?A:无限,或者1~65535次(NETSCAPE2.0)

      image.png

      Header

      前三个字节叫做“文件签名”,固定为'G' 'I' 'F'
      后三个字节标记GIF规范的版本,有"89a"和"87a"两种

      image.png

      Logical Screen Descriptor(LSD)

      包含以下信息:

      • 整个文件的尺寸
      • 是否有全局色表
      • 全局色表的颜色分辨率
      • 色表是否经过了排序
      • 全局色表中颜色的数量
      • 哪种颜色被视为背景色
      • 像素的长宽比
        image.png

      色表

      很多古老的显示设备并不能直接展示RGB数据, 由于硬件的限制,设备会预先将一些颜色加载到内部寄存器中, 每种颜色对应一个编号,传输图像时,不必传输每一个像素的颜色,而只需要传输每一个像素的颜色编号。

      假定色表中最多含有256项
      我们需要利用一些算法,提取出最能代表一张图片的256种颜色,配合抖动算法,让色表绘制出的图片尽量接近原图

      image.png
      原图色彩信息太丰富,会导致难以还原,这就是为什么我们很多时候看到的GIF图片,看起来像是信息缺失或失真的原因。
      image.png

      色表信息 - 颜色分辨率

      颜色分辨率表示原图中一个色彩通道的位数,例如,对于RGB444格式的原图,颜色分辨率为4,RGB888格式的原图,颜色分辨率为8。
      LSD中,颜色分辨率用3位​表示,将这个值+1可以得到实际的颜色分辨率数值, 例如,011代表颜色分辨率为4,111代表颜色分辨率为8
      image.png

      色表信息 - 色表大小

      色表大小表示色表中含有的项目数量,用3位表示,记为n,则实际的项目数量为2(n+1),例如,010代表8,100代表32, 111代表色表大小的上限,即256
      image.png

      色表的格式

      每一项含有RGB三个通道的值, 按照编号顺序排列
      image.png

      全局色表与本地色表

      除了GCT之外,每一帧还可以有自己的色表, 本地色表存在时,忽略全局色表
      image.png

      Image Descriptor

      描述如何绘制一帧图片, 一帧不需要占满整张图片, 图片可能是隔行扫描的
      image.png

      Graphics Control Extension(GCE)

      Graphics Control Extension(GCE)是在GIF89a中加入的,属于可选内容,它含有一些动画相关的属性(包括透明色)
      image.png

      LZW(Lempel–Ziv–Welch)编码

      LZW(Lempel–Ziv–Welch)编码由Abraham Lempel、Jacob Ziv和Terry Welch发明, 用于信息的无损压缩,它于1984年被提出,用固定长度的码,表示不同长度的字符串。

      GIF中的压缩算法采用的是该LZW的一个变体。

      LZW中,字典是不固定的,开始编解码前,会有一个初始字典,编解码时,随着数据的增多,字典也在不断扩充,读入的每一个此前未见过的新字符串,都会被加入到字典中

      如编码过程:

      • 字符串:aabcaac
      • 初始字典:#1->a,#2->b,#3->c
      • 见到aa,是我们没见过的字符串,在字典中增加#4->aa,输出1
      • 见到ab,在字典中增加#5->ab,输出1
      • 见到bc,增加#6->bc,输出2
      • 见到ca,增加#7->ca,输出3;见到aac,增加#8->aac,输出4
      • 最后见到c,输出3
      • 得到112343
      • 它对应的解码过程如下:
      • LZW串:112343
      • 初始字典:#1->a,#2->b,#3->c
      • 见到11,输出aa,并在字典中增加#4->aa
      • 见到23,输出bc,增加#5->ab和#6->bc
      • 见到43,输出aac,增加#7->ca和#8->aac
      • 得到aabcaac

      在GIF中,图像数据的内容是每个像素的颜色编号串,初始字典就是颜色编号对应同值的字典编号,另外加入两个码:Clear Code和End Of Information Code
      以4色图片为例,初始字典为:#1->1,#2->2,#3->3,#4->4,#5->Clear Code(遇到此Code时,清空字典重新开始
      ),#6->EOI code(图片结束)

      图像数据如下图所示,开头是初始字典(除去特殊Code)的位数,接着是数据块的字节数,接着是LZW数据块,以00结尾:

      image.png

      Disposal method

      绘制完一帧之后,如何处理
      0:未定义(可以清空)
      1:不处理
      2:在本帧绘制的区域填上背景色
      3:将本帧绘制的区域还原到绘制之前的状态

      GIF文件格式的优劣

      GIF是一个古老但流行的文件格式,这就概况了GIF这个文件格式的优劣,其优势在于推出时间早,广泛流行,几乎所有的操作系统和浏览器都有对GIF的很好的支持,基于浏览器对于该文件格式的解释,GIF具备加载即播放,同屏多播放等特性,这些都是与同类动态画面的文件(如短视频)所不一致的。而其缺陷在于它的信息承载比很低,同等文件尺寸下分辨率低,画面质量差,还原度差。
      现在有很多互联网公司都在推出新的动图格式如GIFV等,但一个新的格式需要将编解码工作都完成,这就需要既提供文件格式,又需要提供解码环境,虽然已经有一些公司在自己的SDK里面封装了新的动图格式,但这离成为行业标准还有一定距离。

      本文作者:
      李霄,陈敏

      ]]>
      无人车部署方式对比测试报告-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 1引言
      1.1 文档目的
      本文档是咪网停车系统实施过程中的文档,此文档作为测试的指导性方案,用以明确与描述在客户选用阿里云服务时,服务器负载情况,以确保当客户选用阿里云服务可以适用。
      预期参考人员包括:产品用户、测试人员、开发人员、项目管理人员、以及质量管理人员和需要阅读本报告的高层经理。
      1.2 参考文档
      无人车监控客户端_概要设计.xlsx
      无人车监控客户端产品需求文档 .docx
      2 功能测试概要
      2.1 测试用例设计
      场景编号001:
      对“登录注册”模块中的“注册”及“登录”进行测试,要求注册正常,登录登出正常。
      场景编号002
      对“停车数据”模块中的“停车数据”进行测试,要求1平均车位周转率;2近30天停车收费数据总览;3停车方式占比;4停车时间占比功能正常。
      场景编号004
      对“车位管理”模块中的“车位地图”及“车位列表”进行测试,要求1展示车位地图;2全部、违停、空闲、计费数据;3搜索支持;4可创建搜索条件:路段,巡检员,查询是否正确;5车位列表展示;6可分页显示功能正常。
      场景编号005
      对“违停监控”模块中的“取证记录”进行测试,要求1搜索条件检查:2停车路段、巡检员、车辆信息、审核状态、提交时间;3取证记录审核,取消;4数据列表展示功能正常。
      环境编号001
      对阿里云环境及本地环境进行对比测试,主要关注同任务场景下负载情况。
      2.2 测试环境与配置
      数据库:MySql,Sqlserver
      Tomcat:Tomcat7.0
      服务器:阿里云
      2.3 测试方法
      场景法
      边界值
      等价类划分
      因果图等,混合使用
      3性能测试摘要
      3.1压力机器配置
      (IP)地址 操作系统 内存 CPU 硬盘
      10.0.0.55 Linux 16G 8C 100
      3.2被测机器配置
      主机型号 数量 操作系统 处理器 内存 硬盘 网络环境
      阿里云 8 Linux 4C 4GB 100GB 10M共享
      3650M5 1 ESXI/LINUX 32C 64GB 1TB*9 100MB独享不含BGP
      3.3基础数据准备
      咪表200个
      用户50个
      订单20000笔
      3.4性能测试目标要求

      1. 相关测试查询场景要求能支持50并发数
      2. CPU小于等于70%,内存小于等于70%
      3. 单一事务的成功率大于等于99.9%

      3.5 系统监控记录
      序号 测试场景
      (阿里云) cpu(%) RAM DB-CPU DB-RAM
      1 登录 78% 38% 30% 88%
      2 取证记录审核 78% 40% 29% 88%
      3 待付款订单查询 78% 41% 59% 88%
      4 已付款查询 78% 42% 48% 88%
      5 咪表健康 78% 43% 25% 88%

      序号 测试场景
      (本地) cpu(%) RAM DB-CPU DB-RAM
      1 登录 15% 42% 22% 45%
      2 取证记录审核 43% 35% 29% 75%
      3 待付款订单查询 23% 15% 67% 69%
      4 已付款查询 20% 15% 75% 33%
      5 咪表健康 32% 26% 25% 65%

      4 测试结果分析
      4.1 测试结果说明
      若客户仅选用单节点来支撑整体业务的情况,考虑年服务费与设备费用,建议选用阿里云服务器予以支撑。
      仅当客户并发量级超过500,且必须本地存储数据时,考虑采用本地部署方式。
      5.对软件功能的结论
      测试全部通过。

      ]]>
      乘风破浪的阿里云MVP--第13期全球发布-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 阿里云智能总裁张建峰在6月9日阿里云线上峰会上对外展示了阿里云再生长的三大方向:“做深基础”,从飞天云系统操作向下延伸定义硬件;“做厚中台”,将钉钉这样的新型操作系统与阿里云进行深度融合,实现云钉一体;“做强生态”基于云和新型操作系统,构建一个繁荣的应用服务生态。这些方向的引领,离不开阿里云的合作伙伴、离不开我们的生态建设,而阿里云MVP的招募和认证是我们生态建设中不可或缺的一环,这些专家精英在自己熟悉的领域里帮助阿里云积极影响着各行各业的开发者们。

      多人海报.png

      阿里云最有价值专家,简称阿里云 MVP(Most Valuable Professional)
      是积极向上,专注于帮助他人充分了解和使用阿里云技术产品的实践领袖。疫情过后,新基建加速,MVPs与阿里云精准反应,共同协作,不断精进。今天发布13期的全球名单,阿里云充分认可全球的阿里云MVP并对这个温暖的大家庭表示感谢,期待与MVPs一起思考,带领开发者走在世界科技前沿,探索更多的技术真相。

      打破视觉局限,掌握主流技术趋势,此时此刻,我们正在经历的,是一个日益加速的“新秩序时代”因为浪潮不属于逐流者,浪潮只属于对未来保持敏锐、对明天报以观点的人。

      第13期阿里云MVP,共有来自15个国家和地区的31位成功入选,其中缅甸、西班牙、德国、塞尔维亚及哥斯达黎加均为该国首位MVP。内化于心,外化于行,MVPs与阿里云携手共建科技新生态。本期入选MVP平均拥有超过10年的工作经验,覆盖DevOps、AI、大数据、数据库、服务器、云迁移、金融科技等技术与行业领域,厚积科技之力,薄发创新未来,这就是阿里云MVP的风采。

      多人海报.png

      查看更多的阿里云MVP
      MVPs与阿里云一起,在技术建设的道路上不遗余力,不断颠覆。

      阿里云MVP精通多领域技术,愿意主动分享,面向全 世界的开发者和企业,帮助开发者站在巨人肩膀上,帮助企业成为新形态技术顶端的新企业,互联互通,把技术的创造者和使用者链接到一起,构建全球技术新时代

      阿里云和MVPs对全球科技的影响正在继续,更加深刻、快速和广泛,新基建、云计算、IoT、大数据、AI…新技能,新模型,新服务,新应用,新领域,新内容…科技正在创造更多奇迹,继续改变世界。阿里云,阿里云MVP,依然值得期待。
      申请加入阿里云 MVP

      ]]>
      MySQL数据库优化技巧大全-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 MySQL优化三大方向
      ① 优化MySQL所在服务器内核(此优化一般由运维人员完成)。
      ② 对MySQL配置参数进行优化(my.cnf)此优化需要进行压力测试来进行参数调整。
      ③ 对SQL语句以及表优化。
      MySQL参数优化
      1:MySQL 默认的最大连接数为 100,可以在 mysql 客户端使用以下命令查看
      mysql> show variables like 'max_connections';
      2:查看当前访问Mysql的线程
      mysql> show processlist;
      3:设置最大连接数
      mysql>set globle max_connections = 5000;
      最大可设置16384,超过没用
      4:查看当前被使用的connections
      mysql>show globle status like 'max_user_connections'

      申请阿里云服务时,可以使用2000元阿里云代金券,阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui (长期有效)

      对MySQL语句性能优化的16条经验
      ① 为查询缓存优化查询
      ② EXPLAIN 我们的SELECT查询(可以查看执行的行数)
      ③ 当只要一行数据时使用LIMIT 1
      ④ 为搜索字段建立索引
      ⑤ 在Join表的时候使用相当类型的列,并将其索引
      ⑥ 千万不要 ORDER BY RAND ()
      ⑦ 避免SELECT *
      ⑧ 永远为每张表设置一个ID
      ⑨ 可以使用ENUM 而不要VARCHAR
      ⑩ 尽可能的使用NOT NULL
      ⑪ 固定长度的表会更快
      ⑫ 垂直分割
      ⑬ 拆分打的DELETE或INSERT语句
      ⑭ 越小的列会越快
      ⑮ 选择正确的存储引擎
      ⑯ 小心 "永久链接"

      阿里云服务器1核2G低至89元/年,阿里云官活动网址:https://dashi.aliyun.com/site/yun/aliyun

      具体描述如下:
      (一) 使用查询缓存优化查询
      大多数的MySQL服务器都开启了查询缓存。这是提高性能最有效的方法之一,而且这是被MySQL引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放入一个缓存中,这样后续的相同查询就不用操作而直接访问缓存结果了。
      这里最主要的问题是,对于我们程序员来说,这个事情是很容易被忽略的。因为我们某些查询语句会让MySQL不使用缓存,示例如下:
      1:SELECT username FROM user WHERE  signup_date >= CURDATE()
      2:SELECT username FROM user WHERE  signup_date >= '2014-06-24‘
      上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。
      (二) 使用EXPLAIN关键字检测查询
      使用EXPLAIN关键字可以使我们知道MySQL是如何处理SQL语句的,这样可以帮助我们分析我们的查询语句或是表结构的性能瓶颈;EXPLAIN的查询结果还会告诉我们索引主键是如何被利用的,数据表是如何被被搜索或排序的....等等。语法格式是:EXPLAIN +SELECT语句;
      SELECT语句.png

      我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。
      (三)当只要一行数据时使用LIMIT 1
      加上LIMIT 1可以增加性能。MySQL数据库引擎会在查找到一条数据后停止搜索,而不是继续往后查询下一条符合条件的数据记录。
      (四)为搜索字段建立索引
      索引不一定就是给主键或者是唯一的字段,如果在表中,有某个字段经常用来做搜索,需要将其建立索引。
      索引的有关操作如下:
      1.创建索引
      在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。
      1.1> ALTER TABLE
      ALTER TABLE 用来创建普通索引、唯一索引、主键索引和全文索引
      ALTER TABLE table_name ADD INDEX index_name (column_list);
      ALTER TABLE table_name ADD UNIQUE (column_list);
      ALTER TABLE table_name ADD PRIMARY KEY (column_list);
      ALTER TABLE table_name ADD FULLTEXT (column_list);
      其中table_name是要增加索引名的表名,column_list指出对哪些列列进行索引,多列时各列之间使用半角逗号隔开。索引名index_name是可选的,如果不指定索引名称,MySQL将根据第一个索引列自动指定索引名称,另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
      1.2> CREATE INDEX
      CREATE INDEX可对表增加普通索引或UNIQUE索引以及全文索引,但是不可以对表增加主键索引
      CREATE INDEX index_name ON table_name (column_list);
      CREATE UNIQUE index_name ON table_name (column_list);
      CREATE FULLTEXT index_name ON table_name (column_list);
      table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名必须指定。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。
      2.索引类型
      普通索引INDEX:适用于name、email等一般属性
      唯一索引UNIQUE:与普通索引类似,不同的是唯一索引要求索引字段值在表中是唯一的,这一点和主键索引类似,但是不同的是,唯一索引允许有空值。唯一索引一般适用于身份证号码、用户账号等不允许有重复的属性字段上。
      主键索引:其实就是主键,一般在建表时就指定了,不需要额外添加。
      全文检索:只适用于VARCHAR和Text类型的字段。
      注意:全文索引和普通索引是有很大区别的,如果建立的是普通索引,一般会使用like进行模糊查询,只会对查询内容前一部分有效,即只对前面不使用通配符的查询有效,如果前后都有通配符,普通索引将不会起作用。对于全文索引而言在查询时有自己独特的匹配方式,例如我们在对一篇文章的标题和内容进行全文索引时:
      ALTER TABLE article ADD FULLTEXT ('title', 'content'); 在进行检索时就需要使用如下的语法进行检索:
      SELECT * FROM article WHERE MATCH('title', 'content') AGAINST ('查询字符串');
      在使用全文检索时的注意事项:
      MySql自带的全文索引只能用于数据库引擎为MYISAM的数据表,如果是其他数据引擎,则全文索引不会生效。此外,MySql自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文。另外使用MySql自带的全文索引时,如果查询字符串的长度过短将无法得到期望的搜索结果。MySql全文索引所能找到的词默认最小长度为4个字符。另外,如果查询的字符串包含停止词,那么该停止词将会被忽略。
      3.组合索引
      组合索引又称多列索引,就是建立索引时指定多个字段属性。有点类似于字典目录,比如查询 'guo' 这个拼音的字时,首先查找g字母,然后在g的检索范围内查询第二个字母为u的列表,最后在u的范围内查找最后一个字母为o的字。比如组合索引(a,b,c),abc都是排好序的,在任意一段a的下面b都是排好序的,任何一段b下面c都是排好序的
      组合索引的生效原则是 从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用;
      造成断点的原因:
      前边的任意一个索引没有参与查询,后边的全部不生效。
      前边的任意一个索引字段参与的是范围查询,后面的不会生效。
      断点跟索引字字段在SQL语句中的位置前后无关,只与是否存在有关。在网上找到了很好的示例:
      比如:
      where a=3 and b=45 and c=5 .... #这种三个索引顺序使用中间没有断点,全部发挥作用;
      where a=3 and c=5... #这种情况下b就是断点,a发挥了效果,c没有效果
      where b=3 and c=4... #这种情况下a就是断点,在a后面的索引都没有发挥作用,这种写法联合索引没有发挥任何效果;
      where b=45 and a=3 and c=5 .... #这个跟第一个一样,全部发挥作用,abc只要用上了就行,跟写的顺序无关
      (a,b,c) 三个列上加了联合索引(是联合索引 不是在每个列上单独加索引)而是建立了a,(a,b),(a,b,c)三个索引,另外(a,b,c)多列索引和 (a,c,b)是不一样的。
      具体实例可以说明:
      (0) select * from mytable where a=3 and b=5 and c=4;

      abc三个索引都在where条件里面用到了,而且都发挥了作用
      (1) select * from mytable where c=4 and b=6 and a=3;

      这条语句为了说明 组合索引与在SQL中的位置先后无关,where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样
      (2) select * from mytable where a=3 and c=7;

      a用到索引,b没有用,所以c是没有用到索引效果的
      (3) select * from mytable where a=3 and b>7 and c=3;

      a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引
      (4) select * from mytable where b=3 and c=4;

      因为a索引没有使用,所以这里 bc都没有用上索引效果
      (5) select * from mytable where a>4 and b=7 and c=9;

      a用到了 b没有使用,c没有使用
      (6) select * from mytable where a=3 order by b;

      a用到了索引,b在结果排序中也用到了索引的效果,前面说了,a下面任意一段的b是排好序的
      (7) select * from mytable where a=3 order by c;

      a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了,使用 explain 可以看到 filesort
      (8) select * from mytable where b=3 order by a;

      b没有用到索引,排序中a也没有发挥索引效果
      注意:在查询时,MYSQL只能使用一个索引,如果建立的是多个单列的普通索引,在查询时会根据查询的索引字段,从中选择一个限制最严格的单例索引进行查询。别的索引都不会生效。
      4.查看索引
      mysql> show index from tblname;
      mysql> show keys from tblname;
      5.删除索引
      删除索引的mysql格式 :DORP INDEX IndexName ON tab_name;
      注意:不能使用索引的情况
      对于普通索引而言 在使用like进行通配符模糊查询时,如果首尾之间都使用了通配符,索引时无效的。
      假设查询内容的关键词为'abc'
      SELECT * FROM tab_name WHERE index_column LIKE 'abc%'; #索引是有效的
      SELECT * FROM tab_name WHERE index_column LIKE '%abc'; #索引是无效的
      SELECT * FROM tab_name WHERE index_column LIKE '%cba'; #索引是有效的
      SELECT * FROM tab_name WHERE index_column LIKE '%abc%'; #索引是无效的
      当检索的字段内容比较大而且检索内容前后部分都不确定的情况下,可以改为全文索引,并使用特定的检索方式。
      (五)在join表的时候使用相当类型的列,并将其索引
      如果在程序中有很多JOIN查询,应该保证两个表中join的字段时被建立过索引的。这样MySQL颞部会启动优化JOIN的SQL语句的机制。注意:这些被用来JOIN的字段,应该是相同类型的。例如:如果要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)
      例如:
      SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = “user_id”
      两个 state 字段应该是被建过索引的,而且应该是相当的类型,相同的字符集。
      (六)切记不要使用ORDER BY RAND()
      如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)
      (七)避免使用SELECT *
      从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果我们的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。 所以,我们应该养成一个需要什么就取什么的好的习惯。
      Hibernate性能方面就会差,它不用*,但它将整个表的所有字段全查出来
      优点:开发速度快
      (八)永远为每张表设置一个ID主键
      我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的 AUTO_INCREMENT标志。 就算是我们 users 表有一个主键叫 “email”的字段,我们也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在我们的程序中,我们应该使用表的ID来构造我们的数据结构。 而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区…… 在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。
      (九)使用ENUM而不是VARCHAR
      ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。 如果我们有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,我们知道这些字段的取值是有限而且固定的,那么,我们应该使用 ENUM 而不是 VARCHAR。
      (十)尽可能的不要赋值为NULL
      如果不是特殊情况,尽可能的不要使用NULL。在MYSQL中对于INT类型而言,EMPTY是0,而NULL是空值。而在Oracle中 NULL和EMPTY的字符串是一样的。NULL也需要占用存储空间,并且会使我们的程序判断时更加复杂。现实情况是很复杂的,依然会有些情况下,我们需要使用NULL值。 下面摘自MySQL自己的文档: “NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”
      (十一) 固定长度的表会更快
      如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要我们包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。 固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。 并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论我们用不用,他都是要分配那么多的空间。另外在取出值的时候要使用trim去除空格
      (十二)垂直分割
      “垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。
      (十三)拆分大的DELETE或INSERT
      如果我们需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,我们需要非常小心,要避免我们的操作让我们的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。如果我们把我们的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让我们的WEB服务Crash,还可能会让我们的整台服务器马上掛了。所以在使用时使用LIMIT 控制数量操作记录的数量。
      (十四)越小的列会越快
      对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把我们的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。 参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。 如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果我们不需要记录时间,使用 DATE 要比 DATETIME 好得多。
      (十五)选择正确的存储引擎
      在MYSQL中有两个存储引擎MyISAM和InnoDB,每个引擎都有利有弊。
      MyISAM适合于一些需要大量查询的应用,但是对于大量写操作的支持不是很好。甚至一个update语句就会进行锁表操作,这时读取这张表的所有进程都无法进行操作直至写操作完成。另外MyISAM对于SELECT COUNT(*)这类的计算是超快无比的。InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
      MyISAM是MYSQL5.5版本以前默认的存储引擎,基于传统的ISAM类型,支持B-Tree,全文检索,但是不是事务安全的,而且不支持外键。不具有原子性。支持锁表。
      InnoDB是事务型引擎,支持ACID事务(实现4种事务隔离机制)、回滚、崩溃恢复能力、行锁。以及提供与Oracle一致的不加锁的读取方式。InnoDB存储它的表和索引在一个表空间中,表空间可以包含多个文件。
      MyISAM和InnoDB比较,如下图所示:
      下图所示.png

      在5.5之后默认的存储引擎是INNODB
      可以单独进行修改也可以在创建表时修改:
      ALTER TABLE tab_name ENGINE INNODB;
      (十六)小心永久链接
      “永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。
      而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在我们决定要使用“永久链接”之前,我们需要好好地考虑一下我们的整个系统的架构。

      ]]>
      序列化和反序列化-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文来自于千锋教育在阿里云开发者社区学习中心上线课程《Python入门2020最新大课》,主讲人姜伟。

      序列化和反序列化

      通过⽂件操作,我们可以将字符串写⼊到⼀个本地⽂件。但是,如果是⼀个对象(例如列表、字典、元组等),就⽆法直接写⼊到⼀个⽂件⾥,需要对这个对象进⾏序列化,然后才能写⼊到⽂件⾥。

      设计⼀套协议,按照某种规则,把内存中的数据转换为字节序列,保存到⽂件,这就是序列化,反之,从⽂件的字节序列恢复到内存中,就是反序列化。

      Python中提供了JSON和pickle两个模块⽤来实现数据的序列化和反序列化。

      JSON模块

      JSON(JavaScriptObjectNotation, JS对象简谱)是⼀种轻量级的数据交换格式,它基于 ECMAScript 的⼀个⼦集,采⽤完全独⽴于编程语⾔的⽂本格式来存储和表示数据。JSON的本质是字符串!JSON里要使用双引号表示字符串。

      使⽤JSON实现序列化

      JSON提供了dump和dumps⽅法,将⼀个对象进⾏序列化。
      dumps⽅法的作⽤是把对象转换成为字符串,它本身不具备将数据写⼊到⽂件的功能。

      import json
      
      file = open('names.txt', 'w')
      names = ['zhangsan', 'lisi', 'wangwu', 'jerry', 'henry', 'merry', 'chris']
      # file.write(names) 出错,不能直接将列表写⼊到⽂件⾥
      
      # 可以调⽤ json的dumps⽅法,传⼊⼀个对象参数
      result = json.dumps(names)
      
      # dumps ⽅法得到的结果是⼀个字符串
      print(type(result)) # <class 'str'>
      
      # 可以将字符串写⼊到⽂件⾥
      file.write(result)
      
      file.close()

      dump⽅法可以在将对象转换成为字符串的同时,指定⼀个⽂件对象,把转换后的字符串写⼊到这个⽂件⾥。

      import json
      
      file = open('names.txt', 'w')
      names = ['zhangsan', 'lisi', 'wangwu', 'jerry', 'henry', 'merry', 'chris']
      
      # dump⽅法可以接收⼀个⽂件参数,在将对象转换成为字符串的同时写⼊到⽂件⾥
      json.dump(names, file)
      file.close()

      注意:如果是⼀个空对象,调⽤dumps⽅法转换成为⼀个JSON对象,得到的结果是null(JS⾥的空对象)

      json.dumps(None) # null

      使⽤JSON实现反序列化

      使⽤loads和load⽅法,可以将⼀个JSON字符串反序列化成为⼀个Python对象。

      loads⽅法需要⼀个字符串参数,⽤来将⼀个字符串加载成为Python对象。

      import json
      
      # 调⽤loads⽅法,传⼊⼀个字符串,可以将这个字符串加载成为Python对象
      result = json.loads('["zhangsan", "lisi", "wangwu", "jerry", "henry", "merry", "chris"]')
      print(type(result)) # <class 'list'>

      load⽅法可以传⼊⼀个⽂件对象,⽤来将⼀个⽂件对象⾥的数据加载成为Python对象。

      import json
      
      # 以可读⽅式打开⼀个⽂件
      file = open('names.txt', 'r')
      
      # 调⽤load⽅法,将⽂件⾥的内容加载成为⼀个Python对象
      result = json.load(file)
      
      print(result)
      file.close()

      pickle模块

      和json模块类似,pickle模块也有dump和dumps⽅法可以对数据进⾏序列化,同时也有load和loads⽅法进⾏反序列化。区别在于,json模块是将对象转换成为字符串,⽽pickle模块是将对象转换成为⼆进制。

      pickle模块⾥⽅法的使⽤和json⾥⽅法的使⽤⼤致相同,需要注意的是,pickle是将对象转换成为⼆进制,所以,如果想要把内容写⼊到⽂件⾥,这个⽂件必须要以⼆进制的形式打开。
      序列化
      dumps:将Python数据转化为二进制
      dump:将Python数据转化为二进制,同时保存到指定文件

      反序列化
      loads:将二进制加载成为python数据
      load:读取文件,并将文件的二进制内容加载成为python数据

      import pickle
      
      names = ['张三', '李四', '杰克', '亨利']
      b_names = pickle.dumps(names)
      
      file  = open('names.txt', 'wb')
      file.write(b_names)   # 写入的是二进制
      file.close()
      
      file1 = open('names.txt', 'rb')
      x = file1.read()
      y = pickle.loads(x)
      print(y)
      file1.close()
      
      
      file2 = open('names.txt', 'wb')
      pickle.dump(names, file2)
      file2.close()
      
      
      file3 = open('names.txt', 'rb')
      pickle.load(file3)
      class Dog(object):
          def __init__(self, name, color):
              self.name = name
              self.color = color
      
           def eat(self):
              print(self.name + '正在吃东西')
      
      d = Dog('大黄', '白色')
      
      # 保存到文件里
      pickle.dump(d, open('dog.txt', 'wb'))
      
      # 从文件里加载出来
      dd = pickle.load(d, open('dog.txt', 'rb'))
      dd.eat()  
      
      print(dd.name, dd.color)   # 大黄 白色

      区别

      思考: json和pickle两个模块都可以将对象进⾏序列化和反序列化,那它们有哪些区别,在使⽤场景上⼜该如何选择?

      json模块:

      • 将对象转换成为字符串,不管是在哪种操作系统,哪种编程语⾔⾥,字符串都是可识别的。
      • json就是⽤来在不同平台间传递数据的。
      • 并不是所有的对象都可以直接转换成为⼀个字符串,下标列出了Python对象与json字符串的对应关系。

      image.png

      • 如果是⼀个⾃定义对象,默认⽆法装换成为json字符串,需要⼿动指定JSONEncoder。
      • 如果是将⼀个json串重新转换成为对象,这个对象⾥的⽅法就⽆法使⽤了。
      import json
      
      class MyEncode(json.JSONEncoder):
          def default(self, o):
              # return {"name":o.name,"age":o.age}
              return o.__dict__
      
      class Person(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def eat(self):
              print(self.name+'正在吃东⻄')
      
      p1 = Person('zhangsan', 18)
      
      # ⾃定义对象想要转换成为json字符串,需要给这个⾃定义对象指定JSONEncoder
      result = json.dumps(p1, cls=MyEncode)
      print(result) # {"name": "zhangsan", "age": 18}
      
      # 调⽤loads⽅法将对象加载成为⼀个对象以后,得到的结果是⼀个字典
      p = json.loads(result)
      print(type(p))

      pickle模块:

      • pickle序列化是将对象按照⼀定的规则转换成为⼆进制保存,它不能跨平台传递数据。
      • pickle的序列化会将对象的所有数据都保存。

      配套视频

      ]]>
      阿里云服务器如何修改远程端口?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文介绍如何修改 Windows 和 Linux 服务器的默认远程端口。

      修改 Windows 服务器默认远程端口
      本节以 Windows Server 2008 为例介绍如何修改 Windows 服务器默认远程端口。

      远程连接并登录到 Windows 实例。
      运行regedit.exe打开注册表编辑器。
      找到如下注册表子项:HKEY_LOCAL_MACHINESystemCurrentControlSetControlTerminal ServerWinStationsRDP-TcpPortNumber
      找到如下.png

      申请阿里云服务时,可以使用2020元阿里云代金券,阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui (长期有效)

      在弹出的对话框中,选择十进制,在数值数据中输入新的远程端口号,在本例中即 3399。单击确定。
      在弹出的对.png

      (可选)如果您开启了防火墙,需要将新的端口号添加到防火墙并设置允许连接。
      具体方法参见设置 ECS 实例远程连接防火墙。

      阿里云服务器1核2G低至89元/年,阿里云官活动网址:https://dashi.aliyun.com/site/yun/aliyun

      登录 ECS管理控制台,找到该实例,选择更多 > 重启。
      登录 ECS管理.png

      实例重新启动后,在实例的右侧单击管理,进入实例详情页面。选择本实例安全组。
      实例重新启.png

      在安全组列表页面,找到相应的安全组,单击配置规则。
      在安全组规则页面,单击添加安全组规则。根据实际的使用场景来定义安全规则,允许新配置的远程端口进行连接。关于如何设置安全组参见添加安全组规则。
      在安全组规.png

      以上步骤完成后,远程访问服务器,在远程地址后面添加新远程端口号即可连接实例。例如:192.168.1.2:3399。
      以上步骤完.png

      说明 调整 3389 端口后,使用 Mac 的远程桌面连接客户仅支持默认的 3389 端口。

      修改 Linux 服务器默认远程端口
      本节以 CentOS 6.8 为例介绍如何修改 Linux 服务器默认远程端口。

      说明 不要直接修改 22 端口,先添加需要的默认远程端口。之所以先设置成两个端口,测试成功后再关闭一个端口,是为了防止在修改配置文件及网络调试过程中,万一出现新端口无法连接的情况下,还能通过 22 端口进行登录调试。

      远程连接并登录到 Linux 实例。
      运行 vim /etc/ssh/sshd_config 命令。
      在键盘上按“I”键,进入编辑状态。添加新的远程服务端口,本节以 1022 端口为例。在Port 22下输入Port 1022。
      在键盘上按“Esc”,输入:wq退出编辑状态。
      执行以下命令重启实例,之后您可以通过 22 端口和 1022 端口 SSH 登录到 Linux 实例。
      /etc/init.d/sshd restart

      (可选)配置防火墙。使用 CentOS 7 以前的版本并开启默认防火墙 iptables 时,应注意 iptables 默认不拦截访问,如果您配置了 iptables 规则,需要执行iptables -A INPUT -p tcp --dport 1022 -j ACCEPT配置防火墙。然后执行service iptables restart 重启防火墙。
      说明 CentOS 7 以后版本默认安装 Firewalld。如果您已经启用 firewalld.service,需要放行 TCP 1022 端口:运行命令 firewall-cmd --add-port=1022/tcp --permanent。返回结果为 success 即表示已经放行 TCP 1022 端口。

      登录 ECS管理控制台,找到该实例,选择管理。
      进入实例详情页面。选择本实例安全组。
      进入实例详.png

      在安全组列表页面,找到相应的安全组,单击配置规则。
      在安全组规则页面,单击添加安全组规则。根据实际的使用场景来定义安全规则,允许新配置的远程端口进行连接。关于如何设置安全组参见添加安全组规则。
      使用 SSH 工具连接新端口,来测试是否成功。登录时在 Port 一栏输入新修改的端口号,在本例中即 1022。
      使用 SSH.png

      使用 1022 端口连接成功后,再次运行vim /etc/ssh/sshd_config命令,将 Port 22 删除。
      运行 /etc/init.d/sshd restart 命令重启实例,服务器默认远程端口修改完成。再次登录时使用新端口号登录即可。

      ]]>
      牛客融合阿里云,助您找到心仪好工作-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      “您好,可以听到吗?“
      “……. 您…好,我…这里画面卡顿….了!”
      “…..不好意….思,能重复一下您刚才的描述吗吗吗,没听太清….楚…………..”
      “哎,感觉这次又糊了,这已经是我的第99次了]]> 阿里“去 IOE”十二年,弹性计算如何二次去 I 和 E?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 王坚院士曾讲过一句话让人印象深刻,他说「云计算的本质是服务,如果不能将计算资源规模化、大范围地进行共享,如果不能真正以服务的方式提供,就根本算不上云计算。」众所周知,阿里云是完全经历了从 0 到 1,再到 100 的过程,将计算发挥到极致背后有一个关键的服务,那就是弹性计算。

      阿里云弹性计算是阿里云提供的 IaaS 级别云计算服务,它免去了客户采购 IT 硬件的前期准备,让客户像使用水、电、天然气等公共资源一样便捷、高效地使用计算资源,实现计算资源的即开即用和弹性伸缩。在「CSDN 在线峰会 —— 阿里云核心技术竞争力」上,阿里云研究员蒋林泉(花名:雁杨)深入分享了在众多大规模实践下百炼成钢的弹性计算。

      image.png
      演讲者 | 蒋林泉(雁杨),阿里云研究员

      前言:弹性计算 More than just 虚拟机

      一般而言,大家理解的弹性计算,可能首先会想到是虚拟机、云服务器。

      但弹性计算除了是众所周知的 IaaS 的核心——云服务器 ECS 之外,还是一个完整的产品家族,而不只是虚拟机。

      弹性计算不仅是阿里云的大底座,更是阿里巴巴集团的大底座,能够用强大的性能、稳定性、弹性、效率能力来支撑云上客户和阿里云的云产品。目前,中国 80% 的创新企业都在使用我们的弹性计算产品,更有 99% 的阿里云其他产品是在弹性计算产品之上为客户提供服务。

      image.png

      对于弹性计算而言,我们所承担的角色可从三个切面去看待,即制造商、零售(运营)商和服务商:

      1、制造商负责设计虚拟机/云服务器,以满足客户不同的场景需求;
      2、零售(运营)商负责将这些实例售卖出去,提供按量付费、包年包月、RI 以及抢占式实例等多种付费方式,使得售卖形态更加灵活;

      3、服务商则提供大规模集群部署、运维、迁移和弹性伸缩套件产品,实现客户从 Capex 走向 Opex,让客户能够轻松在时间域和空间域上实现扩容,高效交付、部署和运维大规模云服务器。

      零售商(运营商):资源池化&弹性

      我们先从零售商的角度来理解弹性计算。

      对于零售商而言,需要考虑如何将弹性资源卖出去,如何让客户使用这种池化后的弹性资源。

      零售商主要是让用户的服务器从购买变成租赁形态,可以按照年或者月进行付费,这样更符合客户的使用习惯,阿里云也提供按使用量,甚至是通过竞价闲置资源的方式来进行付费,使得客户可以享受到在线下无法实现的付费方式来节约成本。

      在弹性计算的底层提升供应链效率,进行服务器硬件资源虚拟化以及调度,并且保证非常高的 SLA,来给客户提供弹性能力。

      image.png

      ▐ 狭义弹性:时域维度的弹性

      我们先来讲讲狭义的弹性。所谓狭义弹性就是时域维度的弹性。

      如下图中白色条线,这表示的就是时域的弹性,企业上线新特性、年中促销或者日常促销,甚至是业务发展变化很快,后台的计算能力却往往不能很快跟上。

      image.png

      一般传统企业的解决方式其实是提前备货,提前一年甚至三年做预算,进行 IT 资源的储备。其目标是为了保证在未来一到两年内,业务都不会因为容量不够而受损,这也是导致大量线下传统企业的日常 CPU 利用率无法达到 5% 的原因。

      最糟糕的情况是,当有新业务上线需要大规模容量的时候,IT 资源无法支撑,这样的矛盾就会使得上图中间的虚线部分越来越大。因此,传统方式要么就会造成浪费计算资源和资金,要么就无法很好地支撑业务的快速增长。

      ▐ ECS 狭义弹性能力:天下武功,唯快不破

      对于狭义弹性而言,更多需要考虑如何让其跑得更快,当需要资源的时候以最快的速度给到客户。

      目前,阿里云云服务器 ECS 从开启服务器到 SSH 可以登录只需要 22 秒的时间,同时,单位时间内能够交付的计算力面积,可以做到单客户、单 Region 5 分钟 16 万核 vCPU 的交付能力。

      image.png

      ▐ 弹性容量自动伸缩最佳实践

      我们来看看一个弹性容量的最佳实践案例。

      首先,企业客户需要守住自己的一个底座,也就是自己日常流量所需的计算资源,也就是下图中绿色的线,这部分比较适合使用包年包月或者 RI 的模式,因为价格比较便宜。

      而在底座之上的弹性部分则可以使用按量计费或者抢占式的计算资源帮助消除峰值流量,再加上 ESS 的自动化,就能够实现在不同流量峰谷的时候可以自动包裹业务曲线。

      image.png

      ▐ 容量弹性:ESS 弹性自动化 4 种模式

      ESS 弹性自动化提供了 4 种模式,即定时模式、动态模式、手动+动态模式和 AI 预测模式:

      1、定时模式适用于业务波动的时间和范围均可准确预测的场景;

      2、动态模式适合业务波动的时间和范围完全不可预测的场景,根据 CPU 和内存负载情况,进行自动扩容;

      3、手动+动态模式是两者叠加使用,适合业务波动范围较小,但时间不可预测的场景;

      4、AI 预测模式则根据历史资源使用量的峰谷和时域特征拟合,通过智能分析预测来做提前扩容,保证在可能的峰值来临之前,就可以完成资源的准备。

      image.png

      通过多种伸缩模式的灵活组合,能够帮助企业快速响应计划内外的业务变化,实现按需取用,降低成本,自动智能运维,甚至是零运维。

      ▐ 广义弹性:基础设施规模全预铺-空间域的弹性

      第二个维度与大家分享广义弹性。云,特别是像阿里云这么大规模的云,很大的一个特征就是基础设施规模化的全铺设,也就是说具有了空间域的弹性。

      任何一个物理设备,都有扩容上限。当扩张到上限的时候,就会遇到扩容墙的问题,此时就需要设备全部迁移到另外一个地域并重新启动,无法做到跨地域调度。

      云计算则能够实现跨机房、跨可用区,甚至是跨 Region 的扩容。阿里云拥有日不落的数据中心,业务部署到海外也是非常容易的,这就是广义的弹性——空间域的弹性。

      image.png

      ▐ 广义弹性:空间域上覆盖全球的大规模基础设施

      大家经常会听到阿里云部署了多少个 Region 以及多少个 AZ(Availability Zone,可用区),而 AZ 之间是互联的,延时也有严格的保障,因此用户可以突破 IDC 的边界,扩容自己的应用。

      image.png

      ▐ 广义弹性:在 ECS 之上,使用丰富云服务拓展应用的系统支撑能力的弹性

      ECS 会映射到线下的 IDC 服务器,因此无论是数据库还是应用,都是购买软件之后进行交付、运维和使用。对多数云上系统各种 Workload,都可以基于 ECS 用软件自己搭建。

      同时,阿里云还提供了大规模的服务化的云产品,一定会有一款满足你。比如数据库、容器、函数、中间件等都已经实现了服务化,客户不需要去安装、运维和管理这些软件,而能够利用这些软件的弹性实现开箱即用,且按时付费。而且这些软件的数量和质量还不断的进化,因此选择上云还能够为将来拓展应用能力的弹性奠定基础。

      制造商:性能优异,稳如磐石

      客户的应用都在这个云服务器上面,因此性能很重要。云厂商生产了各种不同规格的云服务器,通过 IDC、物理机、网络资源之上的这些操作系统将其切成资源池给到客户。

      这样就像是工业 4.0,客户选择了配置,如内核、CPU、内存、磁盘、操作系统等,阿里云会将这些资源调度到一台机器上,实时生产出来交给用户。

      阿里云提供了封装形态、规格族、规格大小粒度这样广谱覆盖的实例矩阵来覆盖用户在不同场景下对于计算力的需求。

      image.png

      ▐ 制造商成功的本分:稳定性&性能

      中国是个制造业大国,而制造商成功的本分其实就是稳定性和性能。阿里云具有计算、网络、存储性能的稳定性,AZ 内、AZ 间、Region 间以及网络性能的稳定性。

      此外,加上飞天操作系统在计算、存储、网络 3 个底层技术上的不断投入,以及大规模调度系统,结合底层硬件不断进行研发迭代,实现高性能和成本红利。

      image.png

      ▐ 云的稳定性

      云的稳定性主要挑战在两个方面:宕机迁移业务恢复,磁盘损坏不丢数据;硬件批量维修、过保,保证客户对过保无感。

      阿里云将运维和虚拟化解耦,可以做到用户无感的物理硬件替换,对客户业务的连续性打扰降低到非常小的程度,这正是云上核心的稳定性逻辑。

      image.png

      弹性计算的“二次去 I”:用 x86 硬件,挑战小机型稳定性

      下图中数据来自于各厂商官网,阿里云 ECS 单实例可用性 SLA 可以达到 99.975%,跨可用区多实例可用性 SLA 可达到 99.995%。

      image.png

      标题中的“二次去 I”指的是阿里云在服务客户的过程中发现客户单实例对稳定性要求也非常高。

      在“第一次去 IOE”的时候,用的是应用层的分布式技术来解决 x86 的稳定性问题。而在弹性计算领域,则是用基础层的能力去解决 x86 的稳定性问题,目标是用 x86 的硬件做到和小型机一样的稳定性,这就是“二次去 I”。客户的技术能力各不相同,有很大一部分客户对单机的稳定性有非常高的依赖,无法做应用层的容灾,这样严苛的需求就推动阿里云的服务要达到小型机的稳定性,阿里云的基础沉淀了多年,才得以实现这样的业界领先的 SLA。

      弹性计算的云盘快存储“二次去 E”:业界主流厂商云盘可靠性 SLO 一览

      阿里云云盘的可靠性能够做到“9 个 9”,也是目前业界领先的,需要非常严谨和先进的技术架构来保障。通过分布式的基于 x86 的软件定义存储,替代掉原来商业非常昂贵的存储,并达到了存储的高可靠性。

      80% 的宕机,都来自 IDC 电力、IDC 网络和服务器系统三类原因

      阿里云是如何做到上述能力的呢?其实对于服务器而言,80%的宕机,都来自 IDC 电力、IDC 网络和服务器系统三类原因。接下来针对于这三个原因谈谈阿里云所做的事情。

      强健的电力,强健的网络带来强健的 IDC 基础设施

      IDC 掉电的新闻中经常出现,属于高频事件。阿里云在 IDC 的管理上非常严格,拥有高可用电力架构、网络架构以及 3+N 多线 BGP 接入,这也源于多年来的经验和教训,才形成背后成熟的管理体系和技术体系。阿里云帮助客户消除掉了 IDC 机房的大部分电力、网络的可用性威胁。

      image.png

      **
      冗余架构**

      举一个例子,阿里云在服务器和接入交换机的架构上,存储、网络和云盘等都是通过网卡虚拟化出来的。服务器上的电力、网络,都是双线接入的。通过自研技术,在服务器、飞天操作系统中实现物理网络的路由切换。如果这个地方是个单点,没有双路就经常会出现各种各样的问题。阿里云选择在软件侧实现冗余的网络线路路由调度。

      image.png

      批量运维变更故障规避技术平台

      既然是大规模的云,技术迭代和产品更新背后就会有大量的系统发布和变更。如何让其变得更稳定?阿里云背后依赖于非常高精尖的热迁移技术,95% 以上的热迁移,对所有用户都是无感的。

      image.png

      **玄机大脑的异常预测&调度
      **
      飞天操作系统中的玄机大脑能够对于异常进行预测,做提前的热迁移,实现对客户服务的稳定性。

      image.png

      **核心部件的故障预测:准确率和召回率高达 99%
      **
      阿里云核心部件的故障预测的准确率和召回率高达 99%,这背后来自于阿里巴巴这么多年的技术积累。阿里巴巴是一家数据公司,拥有 10 年百万级的服务器打标的高质量数据,再结合达摩院科学家的合作,通过算法训练让这些数据产生价值,提前预测硬件的健康程度,再结合热迁移的能力,实现服务的稳定性。

      成果:70% 的用户可感知故障被消除&规避

      目前,阿里云由于硬件产生的问题中的 70% 都是客户无感的。很多用户使用云服务器已经大约 2 到 3 年的时间了,底层的硬件可能已经更换了 2 至 3 次,但是自己却毫无感知,也没有重启过。这就是使用 x86 的硬件实现了小型机的稳定性。

      image.png

      为何弹性计算如此看重稳定性:进化的力量

      阿里云之所以这么看重稳定性并做了大量的投入,主要是因为技术进化的力量。双 11 一直在考验阿里巴巴集团的系统,不断发现小问题并快速逐一修复。

      此外,还有一个原因就是客户群体的差异,中美的云计算成熟度存在一些差异,在北美或者欧洲,IT 客户群体的能力相对成熟,可以在应用上做容灾,对服务稳定性的依赖相对较小,而国内的 IT 客户群体还不够成熟,很多客户强依赖单机的稳定性,这就让阿里云必须重视稳定性的提升。

      阿里云带着一个梦想,就是能够通过努力,降低中国企业创业的门槛,减小企业数字化转型的阻力,这些都是驱动弹性计算不断进化的驱动力。

      ▐ 云的优异性能

      讲完稳定性,我们来讲讲性能。

      业界领先的 E2E 性能

      虚拟化使得 IT 业务的灵活度、交互效率大大提升,而带来的问题其实就是虚拟化的损耗,这就需要从技术上不断进步。阿里云弹性计算具有业界领先的 E2E 性能,ECS 第六代相比第五代在性能方面在 MySQL、nginx、Redis 等场景下都有了大幅度提升,并且综合性能超越市场同类主售产品。

      image.png

      ECS 性能更强,成本更低的秘密——全链路自研底层技术

      无论是神龙、盘古、洛神,还是阿里自研的 IDC 网络和服务器,这些全链路的融合以及软硬结合,会持续地进行迭代,将技术红利提供给客户和市场。

      image.png

      高性能背后的自研技术:神龙计算平台

      阿里自研的神龙计算平台在 2017 年发布第一代产品,并且每年都在迭代,现在给客户提供服务的是第二代神龙计算平台。第三代马上就会开放给客户,目前处于邀测过程中。

      image.png

      高性能背后的自研技术:神龙、盘古 2.0,物理网络的完美结合

      阿里云是第一个能够把 RDMA 真正用在云上的云厂商,这是因为要解决 RDMA 的规模化问题,存在着大量的挑战。为了享受 RDMA 的技术红利,则必须要在软硬件以及架构上进行大量适配,才能够保证它能够大规模环境下稳定运行。

      image.png

      高性能背后的自研技术:玄机大脑的性能调度

      在高性能的背后,也需要有强大的调度系统,通过玄机大脑和达摩院进行数据化分析,进行大规模调度,让购买小规格实例的客户也能享受到稳定的服务,甚至超出基线的性能。

      image.png

      全球最极端场景对性能、稳定性的打磨

      举一个极端场景的例子,去年双 11 中阿里巴巴的全部业务都已经上云了,峰值会到达无法想象的量级。在平时演练中,会毫无预兆的人为制造各种问题,例如断电、交换机故障、系统故障等场景,弹性计算团队需要在最短的时间内解决出现的问题,这样的考验对于稳定性的打磨是世界独一无二的。

      服务商:云上安全感到效率幸福感

      除了上述谈到的能力之外,用户上云之后还需要交付、部署、配置、运维的服务。就像是开车一样,出了性能好,更需要这种自动驾驶、定速巡航、精准操控等能力辅助。

      image.png

      服务商:集群复杂度和规模变化,对交付、部署、配置、运维效率要求产生质的变化

      上云企业的大小规模不一样,企业规模小的时候,使用的资源数量非常少,自动化要求、管理复杂度、运用复杂度都会很低。在规模变大之后,企业会划分多个部门,具有多个服务和应用,甚至横跨多个地域。海量的集群复杂度、交付复杂度、服务器管理配置的复杂度都会提升,需求就会有质的变化。

      服务商:部署&运维自动化编排,上云用云自动化驾驶

      在云上,为了应对以上挑战,提供了两套配套方案,部署集+ROS 资源编排,和云助手+OOS 运维编排,分别解决规模化、自动化部署配置的能力,还有自动化运维的能力,结合稳定的计算实例、时域和空间域的弹性,以及原子 API 和透明监控事件,进而实现了云上完整弹性。

      image.png

      ROS:用部署编排自动化,轻松交付单元化集群

      举个例子,2020 年 2 月疫情期间,钉钉使用 ROS 进行大规模多集群扩容。保障扩容的顺利完成,首先要有资源扩容,其次要有空间进行扩容,并且还走向海外,需要在海外为其扩容。集群扩容规模非常大,需要大量的时间,所以阿里云通过 ROS 帮助钉钉进行集群扩容,效率提升了 100 倍。而应对如此快速增长的 DAU,背后的弹性能力、快速部署能力、自动化能力都缺一不可。

      OOS:运维编排自动化,云上轻松实现大规模集群高效发布和运维

      这里的例子是一个 1.5 亿客户的大型 App 后端大规模集群发布运维编排,通过 OOS 编排模板去自动刷新资源,帮助其将单次完整线上发布时间从 150 分钟缩短到 10 分钟,并且不需要人工干预和 0 误操作。使得发布频率从每周一次提升到了每天一次,真正地做到了基础设施级别的深度持续发布,使得业务创新效率大幅度提升。

      规模&进化:宝剑锋从磨砺出

      如下图所示的曲线表明规模很重要,规模变大之后,单台服务器的稳定性、弹性、性能大大提升,研发成本也会急剧下降,而阿里云背后强大的技术团队,才让 ECS 有了极致的体验。

      image.png

      弹性计算和阿里云同一年诞生,如今在规模方面已经成为中国乃至亚太的第一,世界第三。弹性计算团队从创建开始,与客户一起成长了 11 年的时间。

      小结:规模化实践驱动的云计算的优势会不断扩大

      大规模实践锻造而成的弹性计算,驱动着云计算市场不断地发展壮大,同时规模化实践驱动的云计算由于其优势,规模也在不断扩大。

      而所有这些竞争力都是构建不同行业的国内外大客户、严苛的云产品内部客户、庞大的集团内部客户的大规模需求驱动之上;构建在阿里云与集团数据和技术积累的肩膀之上;构建在弹性计算这十年中训练出来的强大技术团队,以及在这十年间不断涌现的技术创新之上。

      演讲者简介:蒋林泉是阿里巴巴集团研究员,阿里云弹性计算负责人。过去几年他带领团队,在大规模客户场景的驱动下,对平台技术进行架构升级,让 ECS 的整体产品弹性、性能、稳定性以及功能体验都突飞猛进,弹性计算产品体系在业界具备了很强的竞争力。在负责弹性计算之前,他还曾经担任过阿里云平台架构师,阿里云专有云总架构师,是云计算领域最优秀的架构师之一。

      【END】

      ]]>
      浅谈制造协同的基础设施 - 阿里云数字工厂-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 简介

      制造企业数字化转型,是近年来越来越流行起来的概念,尤其在《工业4.0》和《中国制造2025》概念的引领下,越来越多的企业服务提供商开始涉足该领域,为广大制造业赋能。阿里云作为一个云上基础设施提供商,也不例外的整合了自己在制造企业数字化转型方面已有的信息化改造能力,以及阿里生态在电商销售平台、供应链平台、金融平台、物流平台等多方面的能力,为制造业数字转型的企业、服务商、行业运营商以及区域运营商提供了一个工业互联网领域全面的支撑平台:阿里云数字工厂

      制造业的数字化转型

      制造业的数字化转型,是制造协同及智能制造的基础,制造业从传统的作坊式生产方式步入制造协同及智能制造,一般都需要经过以下步骤:

      数字化

      数字化是基础,简言之就是将纸上的数据,用数字的方式描述出来,将生产经营过程中的所有要素,都用数字及数字间的关系的方式进行表述,这些要素包括:
      人 - 生产经营过程中涉及的人员,如下图的客户,人员,供应商等
      物 - 生产经营过程中涉及的物品,如生产设备,物料等
      事件 - 由人与物的互联,产生的事件,如某某操作员新增了一个物料,某某生产设备的生产活动完成等

      数字化的核心就是建模与描述,对以上要素进行建模,阿里云数字工厂产品里的数据管理功能包括主数据管理工厂建模等,都是围绕这个合理建模的目标展开的。

      数字工厂运营中心
      总的来说,一个典型的工厂的基础数据建模包括:
      企业的组织结构:一个企业包括一个或者多个工厂,工厂又细化分割为不同的部门组织,组织则被定义为拥有不同工作职能的业务实体。
      人员及角色:人员是生产制程过程中重要的基础性单元,根据员工的角色规划不同的系统权限,根据参数设定区分员工的角色和能力,可根据信息制定完善的人员分配和调度计划。
      工作流、操作规范:根据业务实际对产品生产的流程进行定义,即用来定义制造产品的步骤顺序,作为一个标准化的指导。并根据工作流中的每一个工作中心或者工作站的工序标准和要求制定统一化的操作流程,形成唯一的规范。
      物料、物料谱系:定义工厂内部的物料属性:材料、组装件、配件、成品等。并归集同系列的物料成物料分组,形成不同的物料谱系信息。
      制造 BOM、工艺路线:根据产品搭建产品 BOM 架构,并根据产品设计配合工作流 定义和物理模型的设备定义,合理设计产品的工艺路线。规划定义的范围包括:数据记录、变更、工艺监控等。
      在制品状态:定义范围包含在制品数量、产线位置、生产时间、状态等。

      信息化

      在数字化的基础上,人与人之间,物与物之间以及人与物之间,在一定的规则和流程下,产生关联事件,则产生出来了信息,基于这些信息则可以构建上层应用。
      阿里云数字工厂目前有两个官方应用,一个是订单管理,订单管理提供手动、API创建订单、订单查询、订单操作等; 支持面向工厂用户完成生产环节与工序映射, 支持面向生产报工映射;支持导入生产计划单及关联。另一个是产品管理,提供产品创建、编辑,发布产品到主数据、对不再使用的产品进行归档,实现对自己研发、在产及停产的产品进行管理。
      目前看来这两个信息化的应用还是主要集中在销售侧的信息化,而在生产侧还不够完善,对于接到订单之后如何联动后端的生产制造协同处理过程的支持度还不够,举个简单的例子,一个相对更完善的工艺路线管理应该包含对产品制造的工艺过程采用流程图等直观的方式进行定义,支持开始,过程,结束工序的定义,能描述返工或异常处理工序流程,能支持工艺路线的嵌套使用,支持多版本定义。
      虽然如此,目前阿里云数字工厂接口式的开放能力的设计,可以支持第三方开发者去实现更多行业相关的制造协同细节,这个也比较符合toB的平台产品原则: 颗粒度粗,适用范围就广,与行业的结合就浅;颗粒度细,适用范围就窄,与行业的结合就深,阿里云作为平台,走前者的路线也是合理的。

      智能化

      智能化就是系统在具备一定的感知与计算能力之后,能够反向输出预警、决策支持等能力,在阿里云数字工厂里,与之相关的模块有集成组态以及经营驾驶舱

      集成组态页面为车间操作人员提供了可视化的界面,结合云端能力可以远程,多设备的实时查看设备运行状态,风险预警等
      集成组态

      经营驾驶舱则在信息化的基础上,按照企业既定的生产经营指标进行统计与计算,为企业经营者的综合决策提供数据支持
      经营驾驶舱

      总结

      从目前阿里云数字工厂的实现程度来说,已经具备了比较完整完善的云上制造协同基础设施的能力,但对于一般小型工厂来说太过于庞大,而对于生产经营过程复杂和特殊的大型工厂来说,需要有配套的实施甚至定制开发支持才能提供结合度更高更完善的支持,这就需要发挥生态优势,多引入优质的第三方深度应用及第三方实施服务商。

      引用

      阿里云文档 - 工业互联网平台

      ]]>
      如何选择阿里云服务器操作系统?阿里云操作系统说明指南-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800


      随着网站服务器技术的发展,越来越多的站长建站首先选择云服务器。时下阿里云云服务器ECS脱颖而出,成为多数站长网站服务器的首选。那么对于刚刚接触云服务器的站长来说,如何选择适合网站的阿里云云服务器ECS操作系统,阿里云云服务器ECS的操作系统有什么区别,阿里云linux服务器和windows服务器有何不同呢。


      前提:若后期有需求购买阿里云任何产品的朋友,可以提前领取优惠劵。后期可为大家减少成本:点击领取阿里云优惠劵


      阿里云个人购买+阿里云企业购买



      首先,我们要清楚的便是每个系统之间的差别,以及在阿里云上的差别:


      1.Windows


      1.1)系统内含正版激活。


      1.2)适合于运行Windows下开发的程序,如.net等。


      1.3)支持SQLServer等数据库(需自行安装)。


      1.4)可以使用远程桌面方式登录进行管理。


      注:512内存不支持选择Windows系统,1G以上内存才能很好支持该系统。


      2.Linux


      2.1.1)最流行的服务器端操作系统,强大的安全性和稳定性。


      2.1.2)免费且开源,轻松建立和编译源代码。


      2.1.3)通过SSH方式远程访问您的云服务器。


      2.1.4)一般用于高性能web等服务器应用,支持常见的PHP/Python等编程语言,支持MySQL等数据库(需自行安装)。


      2.2CentOS(推荐)请使用yum方式在线安装软件。


      2.3Ubuntu请使用aptitude方式在线安装软件。


      2.4Debian请使用apt-get方式在线安装软件。


      2.5AliyunLinux(兼容RedHat)请使用yum方式在线安装软件,yum源需要自行购买redhat的商业支持。


      操作系统更换规则:


      1.更换操作系统


      更换系统之前请先停止云服务器,云服务器更换操作系统会直接重置系统盘【IP不变】,系统盘数据将会丢失!


      请您注意:


      1.1.更换操作系统会使云服务器的系统盘更换为新的镜像,原有系统盘的数据都会丢失。


      1.2.云服务器数据盘的数据不会受到影响。


      1.3.建议您将系统盘的个人数据备份到数据盘中,或采用其他方式进行备份。


      1.4.因您没有备份系统盘相关个人数据而造成的数据丢失,阿里云不承担责任。


      1.5.内存为512M云服务器不支持更换Windows操作系统。


      2.CPU/内存与操作系统的选择


      2.1)如需选择/变更4G以上内存请您选择64位操作系统(32位操作系统存在寻址限制)。


      2.2)如您选择32位操作系统,4G以上内存页面暂不展示,只有云服务器更换为64位操作系统才可展示。


      2.3)Windows32位操作系统支持最高CPU为4核。


      2.4)配置:[CPU:1核;内存:512M]的云服务器不支持选择/更换Windows操作系统。


      Windows篇


      阿里云提供了6种window系统,涵盖了Server2003sp2以及Server2008R2这两大类操作系统。


      其中又分为了32位和64位


      (1)如何选择32位还是64位


      32位系统相比64位系统,最主要的限制体现在内存的大小上。因为32位本身的限制,其最大只可支持到4GB内存,如果您的网站要使用高于4GB的内存或者以后有扩充内存寻到4GB以上的打算,请使用64位操作系统。


      (2)选择2003还是选择2008


      对于windows来说,我个人建议是选择版本越高的越好。相对来说新版本漏洞相对来说更少,而且IIS7.5相对于IIS6提供了更多的功能以及更方便的控制台。但是考虑到大家的机器配置不同,在此给出一下几种选择:


      A:配置低于双核2GB内存:选择server2003不装数据库配置双核4GB:server2003mssql或者server2008R2不带数据库


      B:配置高于双核8GB:serever2008R2mssql建议如果大家要在云服务器上跑数据库,尽量选择大内存配置,或者降低配置去选用RDS


      (3)中英文、安全加固版如何选择


      这个就依据大家各自的喜好来了,在此不多说了至于Windows服务器配置教程,因为网上教程很多而且相对于Linux来说Windows配置难度更低,所以Windows的配置教程会比较晚的放出。


      Linux篇


      (1)这些linux大类有什么区别


      Debian:用的deb包,使用APT包管理系统。


      同时Debian提供了大多数软件比较新的版本,并且提供了更多的软件包(相对于原版RedHat)。Debian的优点在于更新迅速,软件包完善(Ubuntu尤其),操作便利。缺点是部分时候稳定性欠佳,跟进最新软件有可能存在Bug。


      Centos:用rpm包,使用yum包管理系统。


      相对于Debian来说,Centost的一大特点就是慢。大部分软件停留在稳定版本,而且相距最新版版本也差较多。而且某些新版软件的一些新特性支持也比较慢,比如php-fpm。


      因为Centos是面向企业用户提供的操作系统,所以在稳定性上十分突出,一般在新功能或稳定性的选择上更倾向于后者。只有当某个功能完全确定稳定了,才会加入到系统里。优点是系统稳定,技术文档完善,如果付费的话能得到企业级别的技术支持。缺点是软件包比较老旧,而且一些较新功能会欠缺。


      总结一下:如果你喜欢尝鲜,喜欢用最新的功能或喜欢折腾系统,那么Debian是个更好的选择。
      上手难度Ubunt


      (2)Debian与Ubuntu的选择


      Ubuntu是基于Debian所开发,可以简单地认为Ubuntu是Debian的功能加强版。


      与Debian相比,Ubuntu提供了更人性化系统配置,更强大的系统操作以及比Debian更激进的软件更新。


      Ubuntu与Debian比较,可以认为Debian更趋向于保守一些,Ubuntu对新手友好度更高,上手更容易。


      用过Ubuntu的都会体会到它的易用,反之如果用过Ubuntu再换到别的系统,都会觉得不适应,Ubuntu真的很方便。


      个人建议,如果你打算选择Debian类的,建议选择Ubuntu。


      Ubuntu提供了更好的操作,更激进的软件更新,更方便管理软件以及相差无几的稳定性。


      如果你不想放弃稳定'那么请选择Debian。


      关于Ubuntu版本选择:


      在此解释下Ubuntu的版本支持时间。Ubuntu普通版本只提供18个月的技术支持,过期则不管。


      服务器版本提供长达五年的技术支持。所以建议大家选择12.04版,提供长达5年的技术支持,可以确保在静候相当长的一段时间内你的服务器可以继续收到系统升级补丁以及可用的软件源。


      (3)Centos的选择


      对于阿里云Centos的选择,建议选择Centos6.5版本,带来了更多的新特性以及更多的新功能。


      除非你的软件需要php5.1的环境,那么就选择Centos6.5。如果网站需要支持php5.1,只能选用Centos5.8。


      至于具体版本选择,建议php5.1用户选择Centos5.8,其他的用户则为Centos6.5。
      最后的最后提醒大家一定要领取价值2000优惠劵


      ]]>
      异常的概念-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文来自于千锋教育在阿里云开发者社区学习中心上线课程《Python入门2020最新大课》,主讲人姜伟。

      异常的概念

      程序在运⾏过程中,由于我们的编码不规范,或者其他原因⼀些客观原因,导致我们的程序⽆法继续运⾏,此时,程序就会出现异常。如果我们不对异常进⾏处理,程序可能会由于异常直接中断掉。为了保证程序的健壮性,我们在程序设计⾥提出了异常处理这个概念。
      读取文件异常
      在读取⼀个文件时,如果这个文件不存在,则会报出 FileNotFoundError 错误。
      image.png
      除数为零异常
      出现ZeroDivisionError异常
      image.png
      程序在运⾏过程中会经常遇到类似的异常,如果我们不进⾏处理,此时程序就会中断并退出。为了提⾼程序的健壮性,我们可以使⽤异常处理机制来解决程序运⾏过程中可能出现的问题。

      try...except语句

      try...except语句可以对代码运⾏过程中可能出现的异常进⾏处理。 语法结构:

      try:
          可能会出现异常的代码块
      except 异常的类型:
          出现异常以后的处理语句

      示例:

      try:
          f = open('test.txt', 'r')
          print(f.read())
      except FileNotFoundError:
          print('⽂件没有找到,请检查⽂件名称是否正确')

      try...else语句

      咱们应该对else并不陌⽣,在if中,它的作⽤是当条件不满⾜时执⾏的实⾏;同样在try...except...中也是如此,即如果没有捕获到异常,那么就执⾏else中的事情

      def div(a, b):
          return a / b
      
      try:
          x = div(5 / 0)
      except Exception as e:
          print('程序出错了!!!!')
      else:
          print('计算的结果是', x)

      try..finally语句

      try...finally...语句⽤来表达这样的情况:

      在程序中,如果⼀个段代码必须要执⾏,即⽆论异常是否产⽣都要执⾏,那么此时就需要使⽤finally。⽐如⽂件关闭,释放锁,把数据库连接返还给连接池等。

      try:
          f = open('test.txt')
          try:
              while True:
                  content = f.readline()
                  if len(content) == 0:
                      break
                      print(content)
          except:
              #如果在读取⽂件的过程中,产⽣了异常,那么就会捕获到
              #⽐如 按下了 ctrl+c
              pass
          finally:
              f.close()
              print('关闭⽂件')
      except:
          print("没有这个⽂件")

      说明:

      我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍然被执⾏,把⽂件关闭。

      异常使用场景

      解决输入不是数字以及输入年龄非整数的问题。

      age  = input('请输入您的年龄:')
      
      try:
          age = float(age)
      except ValueError as e:
          print('输入的不是数字')
      else:
          if age > 18:
              print('欢迎来到我的网站')
          else:
              print('未满18岁,请自动离开')

      配套视频

      ]]>
      计算机视觉如何改善我们的日常生活-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      --------点击屏幕右侧或者屏幕底部“+订阅”,关注我,随时分享机器智能最新行业动态及技术干货----------

      人工智能,利用机器学习的力量来处理各种日常任务的技术,已经在改变我们工作、购物、银行和驾驶的方式。

      它给我们带来了一些技术,可以为工人处理平凡、重复的工作,检测欺诈性的金融交易,并允许自动驾驶汽车做出决定。但是人工智能如何改变我们在自己家里管理日常生活的方式呢?

      通过采用Alexa和许多其他物联网设备,我们已经开始向智能技术移交一些责任。

      image.png

      如果我们忘记锁车,我们可以很快纠正错误并远程锁车。如果我们忘记锁上房门或设置DVR来录制喜爱的节目,我们也可以远程处理。

      人工智能技术的下一步将增强它们的实用性。就像它们接管工作场所耗时、无需大脑的日常任务一样,它们将开始从我们家里接手一些苦差事。

      前面仅是一些场景,探索家庭中智能技术的惊人可能性,这些场景可以看到我们需要的东西,并以各种方式帮助我们解决问题。

      轻松购物

      你的智能冰箱在不久的将来有一个摄像头,可以看到牛奶的存量正在减少,或者盒子已经永久地从冰箱中取出,并了解这对你意味着什么。

      冰箱和食品储藏室(也有一个智能摄像头)可交换有关牛奶供应状况的信息。这两个食品存储系统意识到您将要用尽这一必需品,因此向您发送一条文本信息,询问您是否要将其添加到购物清单中。这就是物体识别的魔力。

      为了让您的购物清单保持最新,您的冰箱将信息与洗衣房、浴室、车库和其他任何需要持续存放物品的地方的货架和存储单元进行协调。当需要进行实际的购物时,您所要做的就是检查列表,添加您认为缺失的任何东西并下订单。

      每天的家务活都有很多电器的帮助

      以下是一些日常情况的例子,每天都会出现在某个人身上。他们并不太重要或导致过大压力,但在漫长的工作日、家庭和其他责任之外,又增添了太多的这些小烦恼,使他们开始感到有点沉重。

      1. 洗衣

      你有没有过不小心洗了有污点的东西却没有注意到的经历?那个污点会毁了你的白衬衫。想象一下,如果有一台洗衣机,由于异常检测,它能在你最喜欢的一件衣服上漏了一块污渍时告诉你,例如“灰色运动衫上有芥末渍。 您想预处理吗?”

      再也不会忘记,您将鸡翅掉在您最喜欢的那条紧身牛仔裤上,然后漫不经心地将它们洗了一遍,使污点永生。

      当然,如果污渍没有消失,您的裤子抽屉将告诉您,您需要购买更多的裤子。

      2. 晚餐没主意

      如果你冰箱和食品存储室里的东西不多了。不用担心,两者可以结合起来,提出使用你手头已有物品的食谱建议。这样你就可以专注于下午的会议,而不是纠结于晚餐做什么。

      3. 睡个好觉

      宝宝房间的空调可以将房间保持在一定温度,但这仍然不能告诉您她是太暖还是太冷。智能温度控制可以“看到”孩子额头上的汗水,或者注意到她已经踢开了毯子,或者正在翻来覆去,然后决定为房间降温直到她感到舒适为止。

      相反,如果她太冷,人工智能动作检测可以捕捉到视觉信息,比如把毯子拉在一起。然后它可以检查房间的温度,确定它是有点冷,并决定让房间暖和起来。

      同样的智能温度控制,不仅对于不能自己调节空调的宝宝来说特别方便,同样也适用于爸爸妈妈,他们在房间变得不舒服之前也无法唤醒任何人,他们也需要智能调节温度。

      智能家电给了人类时间的礼物

      智能家电和存储系统可以让工作繁忙的父母们在早上和晚上花更多的时间与他们的孩子和彼此亲密接触,而不用花那么多时间去打理家务。

      智能家居技术有许多节省时间和精力的应用。在这个职业不断发展的时代,时间是我们最需要的东西,人工智能驱动的设备可以减轻我们的负担,还给我们一些宝贵的、失去的时间,是一些最能提高生活质量的技术创新。

      image.png

      文章来源:https://ai.51cto.com/art/202006/619638.htm
      文章转自51cto,本文一切观点和《机器智能技术》圈子无关

      ]]>
      图文详解 DFS 和 BFS-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      原文链接

      一、前言

      深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中。
      本文将会从以下几个方面来讲述深度优先遍历,广度优先遍历,相信大家看了肯定会有收获。

      • 深度优先遍历,广度优先遍历简介
      • 习题演练
      • DFS,BFS 在搜索引擎中的应用

      二、深度优先遍历,广度优先遍历简介

      深度优先遍历

      深度优先遍历主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底...,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
      树是图的一种特例(连通无环的图就是树),接下来我们来看看树用深度优先遍历该怎么遍历。
      image.png
      1、我们从根节点 1 开始遍历,它相邻的节点有 2,3,4,先遍历节点 2,再遍历 2 的子节点 5,然后再遍历 5 的子节点 9。
      image.png
      2、上图中一条路已经走到底了(9是叶子节点,再无可遍历的节点),此时就从 9 回退到上一个节点 5,看下节点 5 是否还有除 9 以外的节点,没有继续回退到 2,2 也没有除 5 以外的节点,回退到 1,1 有除 2 以外的节点 3,所以从节点 3 开始进行深度优先遍历,如下
      image.png
      3、同理从 10 开始往上回溯到 6, 6 没有除 10 以外的子节点,再往上回溯,发现 3 有除 6 以外的子点 7,所以此时会遍历 7
      image.png
      3、从 7 往上回溯到 3, 1,发现 1 还有节点 4 未遍历,所以此时沿着 4, 8 进行遍历,这样就遍历完成了
      完整的节点的遍历顺序如下(节点上的的蓝色数字代表)
      image.png
      相信大家看到以上的遍历不难发现这就是树的前序遍历,实际上不管是前序遍历,还是中序遍历,亦或是后序遍历,都属于深度优先遍历。
      那么深度优先遍历该怎么实现呢,有递归和非递归两种表现形式,接下来我们以二叉树为例来看下如何分别用递归和非递归来实现深度优先遍历。
      1、递归实现
      递归实现比较简单,由于是前序遍历,所以我们依次遍历当前节点,左节点,右节点即可,对于左右节点来说,依次遍历它们的左右节点即可,依此不断递归下去,直到叶节点(递归终止条件),代码如下

      public class Solution {
          private static class Node {
              /**
               * 节点值
               */
              public int value;
              /**
               * 左节点
               */
              public Node left;
              /**
               * 右节点
               */
              public Node right;
              public Node(int value, Node left, Node right) {
                  this.value = value;
                  this.left = left;
                  this.right = right;
              }
          }
          public static void dfs(Node treeNode) {
              if (treeNode == null) {
                  return;
              }
              // 遍历节点
              process(treeNode)
              // 遍历左节点
              dfs(treeNode.left);
              // 遍历右节点
              dfs(treeNode.right);
          }
      }

      递归的表达性很好,也很容易理解,不过如果层级过深,很容易导致栈溢出。所以我们重点看下非递归实现
      2、非递归实现
      仔细观察深度优先遍历的特点,对二叉树来说,由于是先序遍历(先遍历当前节点,再遍历左节点,再遍历右节点),所以我们有如下思路

      1. 对于每个节点来说,先遍历当前节点,然后把右节点压栈,再压左节点(这样弹栈的时候会先拿到左节点遍历,符合深度优先遍历要求)
      2. 弹栈,拿到栈顶的节点,如果节点不为空,重复步骤 1, 如果为空,结束遍历。

      我们以以下二叉树为例来看下如何用栈来实现 DFS。
      image.png
      整体动图如下
      2.gif
      整体思路还是比较清晰的,使用栈来将要遍历的节点压栈,然后出栈后检查此节点是否还有未遍历的节点,有的话压栈,没有的话不断回溯(出栈),有了思路,不难写出如下用栈实现的二叉树的深度优先遍历代码:

      /**
       * 使用栈来实现 dfs
       * @param root
       */
      public static void dfsWithStack(Node root) {
          if (root == null) {
              return;
          }
          Stack<Node> stack = new Stack<>();
          // 先把根节点压栈
          stack.push(root);
          while (!stack.isEmpty()) {
              Node treeNode = stack.pop();
              // 遍历节点
              process(treeNode)
              // 先压右节点
              if (treeNode.right != null) {
                  stack.push(treeNode.right);
              }
              // 再压左节点
              if (treeNode.left != null) {
                  stack.push(treeNode.left);
              }
          }
      }

      可以看到用栈实现深度优先遍历其实代码也不复杂,而且也不用担心递归那样层级过深导致的栈溢出问题。

      广度优先遍历

      广度优先遍历,指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。
      上文所述树的广度优先遍历动图如下,每个节点的值即为它们的遍历顺序。所以广度优先遍历也叫层序遍历,先遍历第一层(节点 1),再遍历第二层(节点 2,3,4),第三层(5,6,7,8),第四层(9,10)。3.gif
      深度优先遍历用的是栈,而广度优先遍历要用队列来实现,我们以下图二叉树为例来看看如何用队列来实现广度优先遍历
      image.png
      动图如下
      4.gif
      相信看了以上动图,不难写出如下代码

      /**
       * 使用队列实现 bfs
       * @param root
       */
      private static void bfs(Node root) {
          if (root == null) {
              return;
          }
          Queue<Node> stack = new LinkedList<>();
          stack.add(root);
          while (!stack.isEmpty()) {
              Node node = stack.poll();
              System.out.println("value = " + node.value);
              Node left = node.left;
              if (left != null) {
                  stack.add(left);
              }
              Node right = node.right;
              if (right != null) {
                  stack.add(right);
              }
          }
      }

      三、习题演练

      接下来我们来看看在 leetcode 中出现的一些使用 DFS,BFS 来解题的题目:

      leetcode 104,111: 给定一个二叉树,找出其最大/最小深度。

      例如:给定二叉树 [3,9,20,null,null,15,7],

      3
         / 
        9  20
          /  
         15   7

      则它的最小深度  2,最大深度 3
      解题思路:这题比较简单,只不过是深度优先遍历的一种变形,只要递归求出左右子树的最大/最小深度即可,深度怎么求,每递归调用一次函数,深度加一。不难写出如下代码

      /**
       * leetcode 104: 求树的最大深度
       * @param node
       * @return
       */
      public static int getMaxDepth(Node node) {
          if (node == null) {
              return 0;
          }
          int leftDepth = getMaxDepth(node.left) + 1;
          int rightDepth = getMaxDepth(node.right) + 1;
          return Math.max(leftDepth, rightDepth);
      }
      /**
       * leetcode 111: 求树的最小深度
       * @param node
       * @return
       */
      public static int getMinDepth(Node node) {
          if (node == null) {
              return 0;
          }
          int leftDepth = getMinDepth(node.left) + 1;
          int rightDepth = getMinDepth(node.right) + 1;
          return Math.min(leftDepth, rightDepth);
      }

      leetcode 102: 给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)。示例,给定二叉树:[3,9,20,null,null,15,7]

      3
         / 
        9  20
          /  
         15   7

      返回其层次遍历结果:

      [
        [3],
        [9,20],
        [15,7]
      ]

      解题思路:显然这道题是广度优先遍历的变种,只需要在广度优先遍历的过程中,把每一层的节点都添加到同一个数组中即可,问题的关键在于遍历同一层节点前,必须事先算出同一层的节点个数有多少(即队列已有元素个数),因为 BFS 用的是队列来实现的,遍历过程中会不断把左右子节点入队,这一点切记!动图如下
      5.gif
      根据以上动图思路不难得出代码如下:
      Java 代码

      /**
       * leetcdoe 102: 二叉树的层序遍历, 使用 bfs
       * @param root
       */
      private static List<List<Integer>> bfsWithBinaryTreeLevelOrderTraversal(Node root) {
          if (root == null) {
              // 根节点为空,说明二叉树不存在,直接返回空数组
              return Arrays.asList();
          }
          // 最终的层序遍历结果
          List<List<Integer>> result = new ArrayList<>();
          Queue<Node> queue = new LinkedList<>();
          queue.offer(root);
          while (!queue.isEmpty()) {
              // 记录每一层
              List<Integer> level = new ArrayList<>();
              int levelNum = queue.size();
              // 遍历当前层的节点
              for (int i = 0; i < levelNum; i++) {
                  Node node = queue.poll();
                  // 队首节点的左右子节点入队,由于 levelNum 是在入队前算的,所以入队的左右节点并不会在当前层被遍历到
                  if (node.left != null) {
                      queue.add(node.left);
                  }
                  if (node.right != null) {
                      queue.add(node.right);
                  }
                  level.add(node.value);
              }
              result.add(level);
          }
          return result;
      }

      Python 代码:

      class Solution:
          def levelOrder(self, root):
              """
              :type root: TreeNode
              :rtype: List[List[int]]
              """
              res = []  #嵌套列表,保存最终结果
              if root is None:
                  return res
              
              from collections import deque
              que = deque([root])  #队列,保存待处理的节点
              while len(que)!=0:
                  lev = []  #列表,保存该层的节点的值
                  thislevel = len(que)  #该层节点个数
                  while thislevel!=0:
                      head = que.popleft()  #弹出队首节点
                      #队首节点的左右孩子入队
                      if head.left is not None:
                          que.append(head.left)
                      if head.right is not None:
                          que.append(head.right)
                      lev.append(head.val)  #队首节点的值压入本层
                      thislevel-=1
                  res.append(lev)
              return res

      这题用 BFS 是显而易见的,但其实也可以用 DFS, 如果在面试中能用 DFS 来处理,会是一个比较大的亮点。
      用 DFS 怎么处理呢,我们知道, DFS 可以用递归来实现,其实只要在递归函数上加上一个「层」的变量即可,只要节点属于这一层,则把这个节点放入相当层的数组里,代码如下:

      private static final List<List<Integer>> TRAVERSAL_LIST  = new ArrayList<>();
      /**
       * leetcdoe 102: 二叉树的层序遍历, 使用 dfs
       * @param root
       * @return
       */
      private static void dfs(Node root, int level) {
          if (root == null) {
              return;
          }
          if (TRAVERSAL_LIST.size() < level + 1) {
              TRAVERSAL_LIST.add(new ArrayList<>());
          }
          List<Integer> levelList = TRAVERSAL_LIST.get(level);
          levelList.add(root.value);
          // 遍历左结点
          dfs(root.left, level + 1);
          // 遍历右结点
          dfs(root.right, level + 1);
      }

      四、DFS,BFS 在搜索引擎中的应用

      我们几乎每天都在 Google, Baidu 这些搜索引擎,那大家知道这些搜索引擎是怎么工作的吗,简单来说有三步
      1、网页抓取
      搜索引擎通过爬虫将网页爬取,获得页面 HTML 代码存入数据库中
      2、预处理
      索引程序对抓取来的页面数据进行文字提取,中文分词,(倒排)索引等处理,以备排名程序使用
      3、排名
      用户输入关键词后,排名程序调用索引数据库数据,计算相关性,然后按一定格式生成搜索结果页面。
      我们重点看下第一步,网页抓取。
      这一步的大致操作如下:给爬虫分配一组起始的网页,我们知道网页里其实也包含了很多超链接,爬虫爬取一个网页后,解析提取出这个网页里的所有超链接,再依次爬取出这些超链接,再提取网页超链接。。。,如此不断重复就能不断根据超链接提取网页。如下图示
      image.png
      如上所示,最终构成了一张图,于是问题就转化为了如何遍历这张图,显然可以用深度优先或广度优先的方式来遍历。
      如果是广度优先遍历,先依次爬取第一层的起始网页,再依次爬取每个网页里的超链接,如果是深度优先遍历,先爬取起始网页 1,再爬取此网页里的链接...,爬取完之后,再爬取起始网页 2...
      实际上爬虫是深度优先与广度优先两种策略一起用的,比如在起始网页里,有些网页比较重要(权重较高),那就先对这个网页做深度优先遍历,遍历完之后再对其他(权重一样的)起始网页做广度优先遍历。

      五、总结

      DFS 和 BFS 是非常重要的两种算法,大家一定要掌握,本文为了方便讲解,只对树做了 DFS,BFS,大家可以试试如果用图的话该怎么写代码,原理其实也是一样,只不过图和树两者的表示形式不同而已,DFS 一般是解决连通性问题,而 BFS 一般是解决最短路径问题,之后有机会我们会一起来学习下并查集,Dijkstra, Prism 算法等,敬请期待!

      来源 | 码海
      作者 | 码海

      ]]>
      阿里高级技术专家:如何结构化地思考、做事、成长?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 图片无法显
      点击图片或戳我查看详情和投简历
      1.png

      作者 | 承风 阿里巴巴高级前端技术专家

      导读:建立结构化的思维,以结构化的模式驱动工作,以结构化的体系构建自身的能力,小到写 PPT、大到为业务提供更大价值,都是非常值得我们使用的模式。阿里巴巴数字供应链事业部高级前端技术专家 - 承风,将会在本文中和大家分享他在建立和践行结构化思维过程中的方法论。

      引言

      在每年自评、汇报、工作中总会感受到一些结构化带来的问题:

      • 老板问我当前做的事情怎么样了,我讲了合作中的难点、视觉风格问题、业务情况、代码质量······工作的进展,说了半小时,老板还是 get 不到我做的事情的情况和价值,是老板不在意这件事、还是我语言表达能力不行?
      • 我这一年做了很多事情,都有一定产出,但是跳出细节来看,发现对业务、对团队价值都不大,是我做得不好、还是运气不好做的事情不好?
      • 最近流行 codeless,我打算研究下可视化搭建;团队业务涉及到流程编排,我打算研究下 TMF······一年下来折腾了不少成果出来,似乎老板也没有很认可,是我不讨老板喜欢还是做的事情没价值?

      这些问题,根据我自己工作经验的总结,认为大都是对结构化认知不足和践行不佳导致的。

      • 第一个问题:对事情的认知和表述结构化方面存在问题 - 结构化的思维相关问题;
      • 第二个问题:做事儿多而杂不成体系 - 结构化的工作模式问题;
      • 第三个问题:学习和成长缺乏重点 - 结构化的能力建设的问题。

      关于结构化

      Structured:建立中心(问题、目标)。以中心的核心要素对中心进行分解,形成分类子结构。以一定的范式、流程顺序进行分类子结构的合理分类、减少非关键分类结构;对关键分类子结构进行分析,寻找对策,制订行动计划。

      同理,逆向的顺序,对多种杂乱的内容,进行分类、剪枝、归纳汇总成一个中心。我认为也是结构化。

      有很多相关的书籍:

      领导者之剑:成功人士的 5 大突破思维技巧、金字塔原理、极简思考:来自世界顶尖咨询公司的高效工作法······

      也可以参看很多结构化的应用方式:结构化面试、结构化金融产品设计、结构化系统开发方法······从多行业多领域的使用可以反思和加深自己的认知。

      在工作中认知和践行结构化

      结构化的理论是简单清晰的(道的层面总是比较简洁),但实际应用中如何进行结构化、最有效的使用结构化却有很多经验(术的层面总是多变的)。在此结合我个人的经验给出一些建议:

      1. 建立中心

      当我们接手一个业务需求、面对一项挑战的时候,应当先思考这个需求的核心目标是干嘛的。

      1)结构化的建立中心

      思考的过程也是结构化的,我通常会分解为两个子结构进行:

      • 这个业务需求当前的目标是什么(事的维度):1. 目标是快速完成上线试一试业务效果:目标事的维度为高效稳定上线;2. 目标是建立后续业务铺开的基础方案:目标事的维度是强架构设计下的核心与功能拆分方案;
      • 为什么需要我来做(人的维度):1. 是因为我工作量还有 buffer 所有承担这部分:目标人的维度是完成职能范畴内的工作;2. 是因为我在这方面技术比较擅长:目标人的维度是利用事情强化自身能力和使用能力把事儿做好。

      2)沿中心上行

      对单个业务需求而言,从事、人两个维度建立起的中心即其核心,是最主要部分,建立一颗结构树的基础。但我们不应当停止于此,还应当向上推导:这个需求在整个业务的范畴内,是在哪一层次,哪一分类的。即应当更高层面、或整体业务和行业发展,对这方面业务是怎样的期许。(价值的维度)

      • 一个团队接手某项业务或需求,其背后都会有思考:我们是期望借着这个业务打造一个平台,提升整体行业的表现;还是突击这个业务方向,占领局部的商业蓝海······
      • 接到一个需求时一定要思考更大层面这事的价值,才能更好的判断优先级、做事模式。

      例如:我们做采购系统,当前需求是,提供采购单列表,按总价范畴搜索单据的能力。按结构化的中心建立,它是:高效稳定上线(事)、我职能范围内的工作(人)。

      • 如果止步于单个需求建立的中心,我们后续的分解应当是如何快速搞定、如何更稳定······
      • 如果我们继续向上构建树,我们可以和产品、使用者深度沟通下为什么要做价格搜索:1. 管理员期望能看到高价订单的情况。那么这个需求的上一个中心节点应当是:管理提效;2. 继续向上,是基于什么原因需要做管理提效?因为防止贪腐、提高工作效率。那么上一个中心节点应当是:降低成本;3. 依次向上,直到抵达整个业务的目标。比如总结觉得,我们的业务是构建一个集成高效的集团采购系统;
      • 再以此反思:1. 降低成本是不是当前工作的重点?团队是否有足够的架构设计和人员组织来支撑?2. 下一步到到管理提效层面,订单的搜索是否真的是当前最佳的提效工具,因为用户如何定义高价格?他执行这种搜索式查阅工作是否真的是有效不遗漏的?查阅到了订单有问题他能做什么?······我们会发现这个需求背后更多的问题。我们也可以沿着更大的中心树,去思考是否构建更好的方案可以更根本的解决这个问题;
         
      • 再回过头来看当前的任务,是否真的是高效稳定上线(事)、我职能范围内的工作(人)。或者当前最紧急的部分(用户直接需求嘛)是高效稳定上线(事)、我职能范围内的工作(人),但后续更要做更多的其他的根本性解决方案。

      2.png

      沿当前的中心向上建立更大的结构化的认知体系:

      • 会让我们对当前事情的判断(中心)更加清晰,也有更好的认知基础,极有利于与合作方的沟通碰撞和内容创新;
      • 建立更大结构化认知体系的过程,也是深入业务、扩展认知力的过程。一定要多和老板、业务方交流,从各自认知的差异性上提升自身的认知能力。

      此外,构建更大认知体系,对个人和团队发展也是有价值的。

      • 很多时候我们忙于业务实现,都没有花时间去思考业务的价值。一部分是因为忙,一部分是因为懒,一部分是因为不懂,一部分是因为我们是来做事拿工资的,而不是带着愿景想把事做好的。这都不是真正能把事情做好的方式;
      • 作为团队的一员,我们不应当只做“花时间、生鸡蛋”的极低人效、技术外包的母鸡模式,而应当积极的尝试做“建机器、铺厂房、出产品”的工厂模式。这对业务和个人的发展才是积极的作用。

      2. 中心的分解

      建立完成中心后,有多种对中心进行分解的方式。其目标在于将中心拆解为多个内聚的子部分。整体思想是 MECE(Mutually Exclusive Collectively Exhaustive)原则,即相互独立,完全穷尽不重叠、不遗漏的分类。够借此有效把握问题的核心,并成为有效解决问题的方法。

      下文是一些分解方案的简介。

      1)SWOT

      SWOT 分析方法又称态势分析法:即 Strengths(优势)、Weaknesses(劣势)、Opportunities(机会)、Threats(威胁)四类。最早用于进行企业竞争态势分析,对个人而言用于分析自身的竞争态势也是极佳的。

      3.png

      (对团队数据可视化能力建设的 SWOT 分析示例)

      SWOT 分析法四个象限可以分别分类四大独立的方面,而其中 SW 部分 - 优势劣势一般用于分析内部条件;OT 部分 - 机会威胁一般用于分析外部情况。又形成了两个独立而全覆盖的大类分隔。非常有助于看清楚当前的情况。

      此外,SWOT 形成的象限又可以结合跨大类进行组合分析:

      • SO 的关联部分,是我们做事的重点,重要紧急的。比如 Node + 可视化能力 =》我们可以构建基础平台;
      • WO 相关的部分,是我们必须解决的,很多情况下是我们需要进行专享突破式学习的内容。比如图形学 + 基础框架 =》如果我们要做基础框架这个机会,那么必须补全图形学相关知识;
      • WT 部分是威胁到这件事或者成长的部分,必须重视、避免、纠正。比如图形学 + 多公司发力基础框架 =》如果我们没有基础框架基础沉淀,又缺乏图形学相关学习目标或者相关引导,那么我们就应当放弃做基础框架;
      • ST 部分是我们直面竞争的部分,如何发挥自身优势去面对威胁,需要有相关的抓手。比如 Node + 成体系的 ISV =》ISV 肯定会在系统化建设上发力,我们必须使用 node 的基础能力提供更加灵活高效的解决方案。

      2)AHP

      AHP 分析方法又称层次分析法:Analytic Hierarchy Process,将与决策总是有关的元素分解成目标、准则、方案等层次,在此基础之上进行定性和定量分析的决策方法。它是一种定性 & 定量结合,系统化 & 层次化的分析方法。

      4.png

      第一层是目标,第二层是分解的准则,第三层是实施方案。构建 A1...A5 与目标相关权重,形成构造判断(成对比较)矩阵。对矩阵进行层次单排序及其一致性检验,再计算 B1....B3 层总排序权值和一致性检验,按照权重结果进行方案优先级的判断。更多详细计算内容可参考 MBALib。

      我们在实际使用中有两种方式:

      • 以层次建模的模式,对核心目标进行有效分解,即如果某一类分解,无法被赋予权重,则不是一个有效的分解;
      • 根据层次分析建模,对当前事情优先级进行决策,在实际应用中我们即便不去精确计算权重,至少按此结构看各个工作目标与分解的相关性,亦是一种指导。

      3)进行分解的顺序逻辑

      中心的分解应当使用流程化思维。指的是找出事情发生的内在逻辑,思考的时候可以逻辑顺序作为参考。

      • 时间顺序:中心执行的步骤、流程等;
      • 结构顺序:中心的空间、地理位置、内部外部条件等;
      • 程度顺序:中心的轻重缓急、重要性等;
      • ······

      以 XMind 为例:

      5.png

      (规划的时间顺序分解)

      6.png

      (按相关性进行结构顺序分解)
      7.png

      (按照重要性即与鱼头的距离进行程度结构分解)

      按照哪种顺序进行分解因个人爱好和事情的不同而不一致,没有优劣之分只有合适不合适。多加应用多做尝试不同模式,会不断提升自身思维和行为的逻辑性以更加结构化。

      3. 清理

      事业是无限的,人力总是有穷、认知高度总是不够的。我们不能把分析出的所有点都做好,也不是分解出的所有层次都真正有价值的。那么针对分解的产出物,应当以数据挖掘的物料准备类似的逻辑进行前期处理,来提高效率、去除噪声。常用的分别为:

      • 泛化:过度细碎的层次应当抽象总结到更高层次,以进行更有效分类;
      • 补漏:针对中心,某些关键决策子层级缺失,应当补充完全;
      • 剪枝:针对中心,与中心紧密度关联较低或无可操作性的部分应当去除,以降低整体分析复杂度。

      1)泛化

      例如我们要提高部门的研发效率,日常工作收集了一些反馈:开发环境不稳定天天抢日常部署,jar 包冲突屡禁不止,经常有人 push origin -f,前后端联调确定字段巨麻烦,对当前业务 webx 用起来不够顺手迅速······

      这些问题都可以归纳到“研发效率提升要解决的点”这个分支下,但细碎的陈列让对问题的解决显得没有重点,后续遇到其他问题也没办法进行有效的区分。

      泛化一般是这样的模式:我们有一些用户年龄,分布为 10、14、35、42、55、72 岁。可以抽象成年龄分层 - 青少年(10、14)、中年(35、42)、老年(55、72),降低数据量提高内聚性。

      针对上面的研发效率问题,我们可以按照研发工作的主要方面,泛化相关的问题:对当前业务 webx 用起来不够顺手迅速(研发架构);抢日常部署、前后端联调(研发环境)、jar 包冲突、强制提交(研发态度)。

      在结构化中,不是越深、越细的结构是越好的,很多时候越内聚抽象的结构反而更有利于进行后续实操改进工作的开展。

      2)补漏

      例如我们要提升前端研发效能。通过调研、学习和思考,认为需要进行几方面的结构化建设:

      • 高效的研发环境:极佳的研发工具、稳定的研发环境、可测试和追述的代码表现、可测试和追述的代码表现······
      • 平台技术驱动研发:核心能力不断沉淀、非核心能力能无侵入的快速定制······
      • 合理的团队人员结构······

      进行结构化的梳理可以更清晰的看出针对目标,哪些部分是我们缺失的。因而针对缺失部分的重要性和紧迫程度,可以更合理的安排工作,而非一味的在较强的部分进行优化、或者各种事情东打一枪西放一炮。

      同理,对个人技术成长而言,整理针对当前行业发展下当前技术环境下个人能力的要求点,进行结构化分层和缺失标注,也是指明自身学习方向的好手段。

      3)剪枝

      在我们进行中心的层层分解时,我们即便做到了归类,也总会生产一些特别发散的点。针对这些点,我们应当进行非关键分类结构的减少,即剪枝过程。

      8.png

      通常需要进行剪枝的部分:

      • 是和其余结构关联不大的部分
      • 针对当前核心问题是非主要相关的部分
      • 兼顾该部分的成本比产出的效果更大

      例如我们要通关只狼,一个牛逼的手柄花费的代价,和其对核心目标的提升是不对等的。如果要兼顾该子结构,可能对自己身体和心理造成更大的负影响,放弃是明智的选择。

      我们在安排自身的学习成长也是类似的,是否需要报个昂贵的视频课程,是否需要深入研究 TMF 源代码,都应当看对当前自身的学习目标的结合度、性价比来做决策,是去是留。

      结构化中,要果断剪枝,保持专注、保持可行性。

      小结

      结构化是一个非常简洁的理论:

      建立中心;
      以中心的核心要素对中心进行分解,形成分类子结构;
      以一定的范式、流程顺序进行分类子结构的合理分类、减少非关键分类结构;
      对关键分类子结构进行分析,寻找对策,制订行动计划。

      我们在思考、做事、成长时应当随时使用,对于梳理复杂问题、进行决策支撑都有很大好处。

      最后回答下最开始的问题:

      • 老板问我当前做的事情怎么样了,我讲了合作中的难点、视觉风格问题、业务情况、代码质量······工作的进展,说了半小时,老板还是 get 不到我做的事情的情况和价值:按照事情核心目标的达成情况,各子部分重点问题和解进行结构化的讲述,两分钟说清楚;
      • 我这一年做了很多事情,都有一定产出,但是跳出细节来看,发现对业务、对团队价值都不大:先建立团队当前业务的核心目标,分解目标达成所需的各个部分,自身努力在某个部分上做深,或者补全当前缺少的部分,或者强力推进将团队目标向上拔高一层更大的目标。
      • 最近流行 codeless,我打算研究下可视化搭建;团队业务涉及到流程编排,我打算研究下 TMF······一年下来折腾了不少成果出来,老板却不提名我晋升:先确定自身技术能力提升的目标,分解到需要提升的各个部分,针对不同部分向老板强求从事相关工作、在当前工作内尝试和深耕,从深入一个部分到横切一个面的能力提升,晋升自然水到渠成。

      课程推荐

      为了更多开发者能够享受到 Serverless 带来的红利,这一次,我们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。

      点击即可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      DataWorks数据处理全流程技术站点地图-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 一图读懂

      基于阿里DataWorks产品实现数据中台数据处理的技术要点梳理。
      基于阿里DataWorks数据处理技术站点地图.png

      主线流程

      1.数据集成

      数据源的多种类型以及数据源管理。

      2.数据交换

      数据交换管理管理和调度管理。

      3.数据开发

      需要实现的核心功能,包含数据处理规则和处理时机。
      在进行数据处理过程中,需要使用的Dataworks产品功能包含数据地图,数据质量,数据保护伞,数据分析等功能。

      4.数据应用

      数据处理结果对外进行数据交换,生成数据API服务以及使用阿里的数据展现工具quickBI和datav进行数据展示。

      ]]>
      应用中心最佳实践之 -- Git数据源发布实战 Fri, 20 Jun 2025 02:20:33 +0800 应用中心最佳实践之 -- Git数据源发布实战

      适用场景

      • 希望使用应用中心
      • 有自己git仓库(包括github,bitbucket,自己搭建的gitlab)
      • 部署模板托管在git

      前置资源准备

      应用中心手动部署托管在Git上的Kubernetes 模板

      这里我们使用此模板作为样例,本模板包含一个Ingress+Service+Deployment,对外通过Ingress提供服务。部署模板的全量链接在此https://github.com/xianlubird/argocd-example-apps/blob/master/ingress-demo/deploy.yaml

      创建应用

      创建应用的所有参数如下所示,这里的部署策略选择手动。
      Git仓库地址需要输入https或者git协议的地址,结尾需要以.git结尾,与git clone 命令需要的地址相同。这里的Git版本就是指branch或者tag,路径指的是存放部署模板的文件夹名称,这里要求部署模板必须存放在文件夹内。
      目标集群默认的In-Cluster 指的就是部署应用中心控制器的本集群,选择后显示的https://kubernetes.default.svc指的为本集群的ApiServer 地址,因为是本集群内部连接,所以使用的是内部Service 名称。命名空间就是希望这个应用安装到哪个命名空间,这里要求必须指定一个存在的命名空间内。
      image.png


      image.png


      部署应用

      创建应用完毕后,我们就可以看到应用的全量拓扑结构,如下图所示:
      image.png

      这里的黄色的小标识OutOfSync代表的意思是,当前模板描述的资源和Kubernetes集群内的实际情况不一致,也就是说,目前的应用模板并没有部署到集群中,下面我们点击右上角的部署按钮,将应用部署到集群中。


      稍等片刻应用就会部署完毕,整个部署过程会实时的展现在用户面前,最终的部署样式如图所示。这里最下方的状态显示为Healthy 和Synced,表示当前模板已经部署到Kubernetes 集群中,且已经符合部署模板的期望状态。
      image.png

      除了查看整个应用的部署拓扑外,我们还可以查看应用的流量结构,点击右上角的图标就可以观察这个应用的流量拓扑情况。
      image.png

      更新应用

      下面我们来演示一下如何更新应用,首先我们在github上更新一下应用模板,将镜像的tag由blue改为green,如这个commit 为例 https://github.com/xianlubird/argocd-example-apps/commit/dbc1296d20b2a92f0fc4826af2448b452d4a40ca。更新完毕后,我们回到应用中心页面,点击刷新按钮,可以看到如下的图片:
      image.png

      这里的OutOfSync就是表明,部署模板已经发生了变化,但是Kubernetes集群内的资源和git上托管的模板不一致,如果想查看具体的版本差异,可以点击版本差异按钮,查看具体的不同点。
      image.png

      这里就有详细的对比,展示当前git上的变更与Kubernetes集群内实际应用资源之间的关系。下面我们就可以部署这个更新。
      image.png

      可以看到部署模板里面,应用中心识别了只有Deployment 发生了变化,我们点击部署就可以完成应用的更新。更新完毕后,可以看到镜像已经变成了我们指定的green
      image.png

      回滚应用

      首先我们查看一下当前发布版本的历史记录,通过点击历史版本就可以看到。
      image.png

      这里会根据git commit和git 提交信息来描述每个版本的具体情况,我们可以选取任何一个版本进行回滚,比如这里我们选取第一个版本进行回滚。


      image.png



      回滚过程中,可以刷新查看具体的情况,回滚完毕后,可以看到镜像已经回到了blue,但是整个应用的状态是OutOfSync,这个是因为和git里面存放的部署模板不一致导致的。


      image.png


      应用中心自动部署托管在Git上的Kubernetes模板

      前面我们介绍了手动的部署应用,每次更新完毕git后,手动的触发应用的更新。下面我们来介绍另一种方式,可以自动化的更新应用。

      创建自动更新的应用

      创建应用与前面的手动更新的应用区别不大,主要就是更新方式的选择,只需要部署策略选择为自动就可以了,其他参数和前面的手动更新应用是一致的。
      这里的修整资源和自我修复我们可以不选择,具体的介绍如下。
      image.png

      创建完毕和上面的应用是相同的,下面我们来看一下更新的操作。

      自动更新应用

      首先我们还是需要去git上更新一下应用,这里我们将green更新为blue,如这个commit 一致 https://github.com/xianlubird/argocd-example-apps/commit/bfaa85784835281e7e30e4d0e88ab7cd9ff5c4b2。更新完毕后,稍等一会,大约1min,刷新一下应用就会发现,应用已经被自动更新,新的镜像为blue,同时发布记录也已经生成,一切都是自动的,不需要用户手动介入任何事情,只要把模板提交上去,就可以完成自动化的发布,同时发布记录也会记录在册。


      image.png


      image.png

      ]]>
      应用中心最佳实践之 -- 多集群多参数发布实战 Fri, 20 Jun 2025 02:20:33 +0800 应用中心最佳实践之 -- 多集群多参数发布实战

      如果一个用户账号下有多个集群的情况,希望将一个应用同时发布到多个集群里面,并且不同集群环境有不同的参数,这里就可以使用应用中心的多集群发布功能。

      添加多个集群

      首先需要在应用中心 -> 配置 -> 集群页面里面,将需要发布的集群添加到应用中心内。这里添加的集群需要kubeconfig,系统会自动读取Kubernetes的配置。我们这里一共添加了三个集群,第一个是本集群,另外的一个是呼和浩特区域的集群,还有一个是上海区域的集群。


      image.png

      为每个集群创建对应的应用

      由于每个应用只能对应一个集群,所以我们需要给每个集群创建对应的集群,这里我们后期会提供一键创建多个集群的功能。


      image.png



      下面可以试用右上角的部署所有应用功能,一键部署此应用到多个环境中。
      image.png





      可以看到,如下图所示,两个应用都已经部署完毕。后面如果在git上更新应用,这两个应用都会
      image.png

      ]]>
      应用中心最佳实践之—— Helm编排应用的多集群部署实战 Fri, 20 Jun 2025 02:20:33 +0800 如果一个用户账号下有多个集群的情况,希望将一个应用同时发布到多个集群里面,并且不同集群环境有不同的参数,这里就可以使用应用中心的多集群发布功能。

      添加多个目标集群

      应用中心 -> 配置 -> 集群,将需要发布的集群添加到应用中心内。这里添加的集群需要kubeconfig,系统会自动读取Kubernetes的配置。我们这里一共添加了三个集群,第一个是本集群,另外的一个是北京区域的集群,还有一个是香港区域的集群。
      1592817023181-7bf39d68-f556-4464-9ef1-829cd2439212.png

      添加示例git repo

      示例git repo地址: https://github.com/haoshuwei/appcenter-samples.git
      编排模板路径:examples/demo-helm
      1.png
      1.png

      应用中心 -> 配置 -> 仓库 -> 连接Repo

      创建应用demo-helm-pre和demo-helm-pro
      demo-helm-pre应用部署至目标集群ack-pre-beijing并指定使用values.yaml文件进行参数渲染
      1.png

      demo-helm-pro应用部署至目标集群ack-pre-hk并指定使用values-pro.yaml文件进行参数渲染
      1.png

      以下为2个应用创建参数配置对比

      通用-应用名称 demo-helm-pre demo-helm-pro
      通用-部署策略 手动 手动
      源-类型 Git Git
      源-版本 https://github.com/haoshuwei/appcenter-samples.git https://github.com/haoshuwei/appcenter-samples.git
      源-路径 master master
      目标集群-集群 examples/demo-helm examples/demo-helm
      目标集群-命名空间 ack-pre-beijing ack-pro-hk
      源-仓库地址 demo-helm demo-helm
      Helm-VALUES values.yaml values-pro.yaml

      一键部署应用到多个集群

      应用中心 -> 应用 -> 部署所有应用
      1.png

      勾选需要部署的demo-helm-pre和demo-helm-pro 2个应用并点击部署
      1.png

      应用部署成功
      1.png

      ]]>
      应用中心最佳实践之 —— Template数据源发布实战 Fri, 20 Jun 2025 02:20:33 +0800 适用场景

      • 希望使用应用中心
      • 由于安全合规等问题无法将应用部署模板托管在git仓库中

      前置资源准备

      已经开通应用中心服务,参考链接 https://help.aliyun.com/document_detail/169923.html

      创建Template数据源

      容器服务控制台 -> 市场 -> 编排模板
      1.png

      创建模板appcenter-template-demo,包含deployment、service和ingress 3个资源

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: demo
        labels:
          app: demo
      spec:
        minReadySeconds: 5
        revisionHistoryLimit: 5
        progressDeadlineSeconds: 60
        strategy:
          rollingUpdate:
            maxUnavailable: 1
          type: RollingUpdate
        selector:
          matchLabels:
            app: demo
        template:
          metadata:
            annotations:
              prometheus.io/scrape: "true"
              prometheus.io/port: "9797"
            labels:
              app: demo
          spec:
            containers:
            - name: demo
              image: registry.cn-hangzhou.aliyuncs.com/acs/rollouts-demo:blue
              imagePullPolicy: IfNotPresent
              ports:
              - name: http
                containerPort: 8080
                protocol: TCP
              readinessProbe:
                tcpSocket:
                  port: 8080
                initialDelaySeconds: 5
                timeoutSeconds: 5
              resources:
                limits:
                  cpu: 2000m
                  memory: 512Mi
                requests:
                  cpu: 100m
                  memory: 64Mi
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: demo-svc
      spec:
        selector:
          app: demo
        ports:
          - protocol: TCP
            port: 80
            targetPort: 8080
      ---
      apiVersion: extensions/v1beta1
      kind: Ingress
      metadata:
        name: demo
        labels:
          app: demo
      spec:
        rules:
          - host: app.demo.example.com
            http:
              paths:
                - backend:
                    serviceName: demo-svc
                    servicePort: 80

      创建完毕
      1.png

      创建应用

      应用中心 -> 应用 -> 创建应用
      1.png

      通用

      应用名称: appcenter-template-demo
      部署策略: 手动
      1.png

      类型:自定义模板
      模板:appcenter-template-demo
      1.png

      目标集群

      集群:in-cluster(https://kubernetes.default.svc)
      命名空间:demo-template
      1.png

      点击 创建
      1.png

      部署应用

      点击应用可以查看应用详情, 下图是示例应用的Kubernetes资源全量拓扑结构
      1.png

      点击右上角的部署, 默认勾选的要部署资源有deployment、service和ingress资源
      1.png
      这里的黄色的小标识OutOfSync代表的意思是,当前模板描述的资源和Kubernetes集群内的实际情况不一致,也就是说,目前的应用模板并没有部署到集群中,下面我们点击右上角的部署按钮,将应用部署到集群中。
      点击部署完成应用部署

      稍等片刻应用就会部署完毕,整个部署过程会实时的展现在用户面前,最终的部署样式如图所示。这里最下方的状态显示为Healthy 和Synced,表示当前模板已经部署到Kubernetes 集群中,且已经符合部署模板的期望状态。
      1.png

      除了查看整个应用的部署拓扑外,我们还可以查看应用的流量结构,点击右上角的图标就可以观察这个应用的流量拓扑情况。
      1.png

      更新应用

      下面我们来演示一下如何更新应用, 首先需要在编排模板中更新appcenter-template-demo, 本示例中我们把Deployment资源中的image tag从blue改为red,此时您会发现appcenter-template-demo模板有了新的历史版本记录
      1.png
      此时回到应用中心页面,手动点击 刷新 -> 强制刷新(若部署策略为自动,则应用中心每3分钟自动检查数据源更新)
      可以看到应用状态为OutOfSync, 意为当前环境中应用状态与数据源中所声明的状态不一致
      1.png

      点击 版本差异 可以查看本次变更的详细内容
      1.png

      再次点击部署,更新集群环境中的应用到最新版本
      1.png

      可以看到应用已经更新为image tag为red的新版本
      1.png

      应用回滚

      历史版本/回滚 -> 选择需要回滚的历史版本 -> 回滚
      1.png

      回滚过程中,可以刷新查看具体的情况,回滚完毕后,可以看到镜像已经回到了blue,但是整个应用的状态是OutOfSync,这个是因为和git里面存放的部署模板不一致导致的。
      1.png

      ]]>
      基于ASM的GRPC服务部署实践 Fri, 20 Jun 2025 02:20:33 +0800 继MicroServices之后,ServiceMesh是又一个推动软件工业的革命性技术。其服务治理的方法论,不仅改变了技术实现的方式和社会分工。

      运行于数据平面的用户服务与治理服务的各种规则彻底解耦。运行于控制平面的规则定义组件,将流量控制的具体规则推送给运行于数据平面的proxy,proxy通过对用户服务的ingress和egress的实际控制,最终实现服务治理。

      原本需要服务开发者编程实现的服务发现、容错、灰度、流量复制等能力,被ServiceMesh非侵入的方式实现。此外,ServiceMesh还提供了访问控制、认证授权等功能,进一步减轻了用户服务的开发成本。

      阿里云提供的服务网格(ASM)是基于容器服务(ACK)之上的托管版ServiceMesh,在提供完整的ServiceMesh能力的同时(ASM还在底层横向拉通了阿里云云原生的各种能力,不在本篇讲述范围),免去了用户搭建和运维ServiceMesh平台istio的繁琐工作。本篇将分享如何将我们自己的GRPC服务,托管到阿里云的服务网格中。

      1. grpc服务

      grpc协议相比http而言,既具备http跨操作系统和编程语言的好处,又提供了基于流的通信优势。而且,grpc逐渐成为工业界的标准,一旦我们的grpc服务可以mesh化,那么更多的非标准协议就可以通过转为grpc协议的方式,低成本地接入服务网格,实现跨技术栈的服务通信。

      grpc服务的示例部分使用最普遍的编程语言Java及最高效的编程框架SpringBoot。示例的拓扑示意如下:

      1.grpc.png

      1.1 springboot

      common——proto2java

      示例工程包含三个模块,分别是commonproviderconsumer。其中,common负责将定义grpc服务的protobuf转换为java的rpc模板代码;后两者对其依赖,分别实现grpc的服务端和客户端。

      示例工程的protobuf定义如下,实现了两个方法SayHelloSayByeSayHello的入参是一个字符串,返回一个字符串;SayBye只有一个字符串类型的出参。

      syntax = "proto3";
      import "google/protobuf/empty.proto";
      package org.feuyeux.grpc;
      
      option java_multiple_files = true;
      option java_package = "org.feuyeux.grpc.proto";
      
      service Greeter {
        rpc SayHello (HelloRequest) returns (HelloReply) {}
        rpc SayBye (google.protobuf.Empty) returns (HelloReply) {}
      }
      
      message HelloRequest {
        string name = 1;
      }
      
      message HelloReply {
        string reply = 1;
      }

      common构建过程使用protobuf-maven-plugin自动生成rpc模板代码。

      provider——grpc-spring-boot-starter

      provider依赖grpc-spring-boot-starter包以最小化编码,实现grpc服务端逻辑。示例实现了两套grpc方法,以在后文演示不同流量的返回结果不同。

      第一套方法示意如下:

      @GRpcService
      public class GreeterImpl extends GreeterImplBase {
      
          @Override
          public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
              String message = "Hello " + request.getName() + "!";
              HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
              responseObserver.onNext(helloReply);
              responseObserver.onCompleted();
          }
      
          @Override
          public void sayBye(com.google.protobuf.Empty request, StreamObserver<HelloReply> responseObserver) {
              String message = "Bye bye!";
              HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
              responseObserver.onNext(helloReply);
              responseObserver.onCompleted();
          }
      }

      第二套方法示意如下:

      @GRpcService
      public class GreeterImpl2 extends GreeterImplBase {
      
          @Override
          public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
              String message = "Bonjour " + request.getName() + "!";
              HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
              responseObserver.onNext(helloReply);
              responseObserver.onCompleted();
          }
      
          @Override
          public void sayBye(com.google.protobuf.Empty request, StreamObserver<HelloReply> responseObserver) {
              String message = "au revoir!";
              HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
              responseObserver.onNext(helloReply);
              responseObserver.onCompleted();
          }
      }

      consumer——RESTful

      consumer的作用有两个,一个是对外暴露RESTful服务,一个是作为grpc的客户端调用grpc服务端provider。示意代码如下:

      @RestController
      public class GreeterController {
          private static String GRPC_PROVIDER_HOST;
      
          static {
              GRPC_PROVIDER_HOST = System.getenv("GRPC_PROVIDER_HOST");
              if (GRPC_PROVIDER_HOST == null || GRPC_PROVIDER_HOST.isEmpty()) {
                  GRPC_PROVIDER_HOST = "provider";
              }
              LOGGER.info("GRPC_PROVIDER_HOST={}", GRPC_PROVIDER_HOST);
          }
      
          @GetMapping(path = "/hello/{msg}")
          public String sayHello(@PathVariable String msg) {
              final ManagedChannel channel = ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)
                      .usePlaintext()
                      .build();
              final GreeterGrpc.GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
              ListenableFuture<HelloReply> future = stub.sayHello(HelloRequest.newBuilder().setName(msg).build());
              try {
                  return future.get().getReply();
              } catch (InterruptedException | ExecutionException e) {
                  LOGGER.error("", e);
                  return "ERROR";
              }
          }
      
          @GetMapping("bye")
          public String sayBye() {
              final ManagedChannel channel = ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)
                      .usePlaintext()
                      .build();
              final GreeterGrpc.GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
              ListenableFuture<HelloReply> future = stub.sayBye(Empty.newBuilder().build());
              try {
                  return future.get().getReply();
              } catch (InterruptedException | ExecutionException e) {
                  LOGGER.error("", e);
                  return "ERROR";
              }
          }
      }

      这里需要注意的是GRPC_PROVIDER_HOST变量,我们在ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)中使用到这个变量,以获得provider服务的地址。相信你已经发现,服务开发过程中,我们没有进行任何服务发现能力的开发,而是从系统环境变量里获取这个值。而且,在该值为空时,我们使用了一个hardcode值provider。没错,这个值将是后文配置在isito中的provider服务的约定值。

      1.2 curl&grpcurl

      本节将讲述示例工程的本地启动和验证。首先我们通过如下脚本构建和启动provider和consumer服务:

      # terminal 1
      mvn clean install -DskipTests -U
      java -jar provider/target/provider-1.0.0.jar
      
      # terminal 2
      export GRPC_PROVIDER_HOST=localhost
      java -jar consumer/target/consumer-1.0.0.jar

      我们使用curl以http的方式请求consumer:

      # terminal 3
      $ curl localhost:9001/hello/feuyeux
      
      Hello feuyeux!
      
      $ curl localhost:9001/bye
      
      Bye bye!

      最后我们使用grpcurl直接测试provider:

      $ grpcurl -plaintext -d @ localhost:6565 org.feuyeux.grpc.Greeter/SayHello <<EOM   
      {
        "name":"feuyeux"
      }
      EOM
      
      {
        "reply": "Hello feuyeux!"
      }
      
      $ grpcurl -plaintext localhost:6565 org.feuyeux.grpc.Greeter/SayBye                                                                                                 
      {
        "reply": "Bye bye!"
      }

      1.2 docker

      服务验证通过后,我们制作三个docker镜像,以作为deployment部署到kubernetes上。这里以provider的dockerfile为例:

      FROM openjdk:8-jdk-alpine
      ARG JAR_FILE=provider-1.0.0.jar
      COPY ${JAR_FILE} provider.jar
      COPY grpcurl /usr/bin/grpcurl
      ENTRYPOINT ["java","-jar","/provider.jar"]

      构建镜像和推送到远端仓库的脚本示意如下:

      docker build -f grpc.provider.dockerfile -t feuyeux/grpc_provider_v1:1.0.0 .
      docker build -f grpc.provider.dockerfile -t feuyeux/grpc_provider_v2:1.0.0 .
      docker build -f grpc.consumer.dockerfile -t feuyeux/grpc_consumer:1.0.0 .
      
      docker push feuyeux/grpc_provider_v1:1.0.0
      docker push feuyeux/grpc_provider_v2:1.0.0
      docker push feuyeux/grpc_consumer:1.0.0

      本地启动服务验证,示意如下:

      # terminal 1
      docker run --name provider2 -p 6565:6565 feuyeux/grpc_provider_v2:1.0.0
      
      # terminal 2
      docker exec -it provider2 sh
      grpcurl -v -plaintext localhost:6565 org.feuyeux.grpc.Greeter/SayBye
      exit
      # terminal 3
      export LOCAL=$(ipconfig getifaddr en0)
      docker run --name consumer -e GRPC_PROVIDER_HOST=${LOCAL} -p 9001:9001 feuyeux/grpc_consumer
      # terminal 4
      curl -i localhost:9001/bye

      1.3 istio

      验证完镜像后,我们进入重点。本节将完整讲述如下拓扑的服务治理配置:
      2.mesh-arch.png

      Deployment

      consumer的deployment声明示意如下:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        labels:
          app: consumer
          version: v1
      ...
            containers:
              - name: consumer
                image: feuyeux/grpc_consumer:1.0.0
                imagePullPolicy: IfNotPresent
                ports:
                  - containerPort: 9001

      provider1的deployment声明示意如下:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: provider-v1
        labels:
          app: provider
          version: v1
      ...
            containers:
              - name: provider
                image: feuyeux/grpc_provider_v1:1.0.0
                imagePullPolicy: IfNotPresent
                ports:
                  - containerPort: 6565

      provider2的deployment声明示意如下:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: provider-v2
        labels:
          app: provider
          version: v2
      ...
            containers:
              - name: provider
                image: feuyeux/grpc_provider_v2:1.0.0
                imagePullPolicy: IfNotPresent
                ports:
                  - containerPort: 6565

      Deployment中使用到了前文构建的三个镜像。在容器服务中不存在时(IfNotPresent)即会拉取。

      这里需要注意的是,provider1和provider2定义的labels.app都是provider,这个标签是provider的唯一标识,只有相同才能被Service的Selector找到并认为是一个服务的两个版本。

      服务发现

      provider的Service声明示意如下:

      apiVersion: v1
      kind: Service
      metadata:
        name: provider
        labels:
          app: provider
          service: provider
      spec:
        ports:
          - port: 6565
            name: grpc
            protocol: TCP
        selector:
          app: provider

      前文已经讲到,服务开发者并不实现服务注册和服务发现的功能,也就是说示例工程不需要诸如zookeeper/etcd/Consul等组件的客户端调用实现。Service的域名将作为服务注册的名称,服务发现时通过这个名称就能找到相应的实例。因此,前文我们直接使用了hardcode的provider

      grpc路由

      服务治理的经典场景是对http协议的服务,通过匹配方法路径前缀来路由不同的RESTful方法。grpc的路由方式与此类似,它是通过http2实现的。grpc的service接口及方法名与 http2的对应形式是`Path : /Service-Name/{method name}。因此,我们可以为Gateway的VirtualService定义如下的匹配规则:

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: grpc-gw-vs
      spec:
        hosts:
          - "*"
        gateways:
          - grpc-gateway
        http:
      ...
          - match:
              - uri:
                  prefix: /org.feuyeux.grpc.Greeter/SayBye
              - uri:
                  prefix: /org.feuyeux.grpc.Greeter/SayHello

      AB流量

      掌握了grpc通过路径的方式路由,定义AB流量便水到渠成:

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: provider
      spec:
        gateways:
          - grpc-gateway
        hosts:
          - provider
        http:
          - match:
              - uri:
                  prefix: /org.feuyeux.grpc.Greeter/SayHello
            name: hello-routes
            route:
              - destination:
                  host: provider
                  subset: v1
                weight: 50
              - destination:
                  host: provider
                  subset: v2
                weight: 50
          - match:
              - uri:
                  prefix: /org.feuyeux.grpc.Greeter/SayBye
            name: bye-route
      ...

      到此,示例工程的核心能力简单扼要地讲述完毕。详细代码请clone本示例工程。接下来,我将介绍如何将我们的grpc服务实例部署到阿里云服务网格。

      2. 服务网格实践

      2.1 托管集群

      首先使用阿里云账号登录,进入容器服务控制台(https://cs.console.aliyun.com),创建Kubernetes集群-标准托管集群。详情见帮助文档:快速创建Kubernetes托管版集群

      2.2 服务网格

      进入服务网格控制台(https://servicemesh.console.aliyun.com>),创建服务网格实例。详情见帮助文档:[服务网格 ASM > 快速入门 使用流程](https://help.aliyun.com/document_detail/149552.html)

      服务网格实例创建成功后,确保数据平面已经添加容器服务集群。然后开始数据平面的配置。

      2.3 数据平面

      kubeconfig

      在执行数据平面的部署前,我们先确认下即将用到的两个kubeconfig。

      • 进入容器实例界面,获取kubconfig,并保存到本地~/shop/bj_config
      • 进入服务网格实例界面,点击连接配置,获取kubconfig,并保存到本地~/shop/bj_asm_config

      请注意,在数据平面部署过程中,我们使用~/shop/bj_config这个kubeconfig;在控制平面的部署中,我们使用~/shop/bj_asm_config这个kubeconfig。

      设置自动注入

      kubectl 
      --kubeconfig ~/shop/bj_config 
      label namespace default istio-injection=enabled

      可以通过访问容器服务的命名空间界面进行验证。

      部署deployment和service

      export DEMO_HOME=
      
      kubectl 
      --kubeconfig ~/shop/bj_config 
      apply -f $DEMO_HOME/istio/kube/consumer.yaml
      
      kubectl 
      --kubeconfig ~/shop/bj_config 
      apply -f $DEMO_HOME/istio/kube/provider1.yaml
      
      kubectl 
      --kubeconfig ~/shop/bj_config 
      apply -f $DEMO_HOME/istio/kube/provider2.yaml

      可以通过访问容器服务的如下界面进行验证:

      通过如下命令,确认pod的状态是否符合预期:

      $ kubectl 
      --kubeconfig ~/shop/bj_config 
      get pod
      
      NAME                           READY   STATUS    RESTARTS   AGE
      consumer-v1-5c565d57f-vb8qb    2/2     Running   0          7h24m
      provider-v1-54dbbb65d8-lzfnj   2/2     Running   0          7h24m
      provider-v2-9fdf7bd6b-58d4v    2/2     Running   0          7h24m

      入口网关服务

      最后,我们通过ASM管控台配置入口网关服务,以对外公开http协议的9001端口和grpc协议的6565端口。

      image.png

      创建完成后,我们就有了公网的IP。余文测试验证环节将使用到这里配置的入口网关IP 39.102.37.176

      image.png

      2.4 控制平面

      部署Gateway

      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/gateway.yaml

      部署完毕后,在ASM控制台的控制平面-服务网关界面下,可以看到这个Gateway实例。也可以直接使用该界面创建和删除服务网格的Gateway实例。
      image.png

      部署VirtualService

      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/gateway-virtual-service.yaml
      
      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/provider-virtual-service.yaml
      
      
      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/consumer-virtual-service.yaml

      部署完毕后,在ASM控制台的控制平面-虚拟服务界面下,可以看到VirtualService实例列表。也可以直接使用界面创建和删除服务网格的VirtualService实例。
      image.png

      部署DestinationRule

      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/provider-destination-rule.yaml
      
      kubectl 
      --kubeconfig ~/shop/bj_asm_config 
      apply -f $DEMO_HOME/istio/networking/consumer-destination-rule.yaml

      部署完毕后,在ASM控制台的控制平面-目标规则界面下,可以看到DestinationRule实例列表。也可以直接使用界面创建和删除服务网格的DestinationRule实例。

      image.png

      2.5 流量验证

      完成grpc服务在ASM的部署后,我们首先验证如下链路的流量:
      image.png

      HOST=39.102.37.176
      for ((i=1;i<=10;i++)) ;  
      do   
      curl ${HOST}:9001/hello/feuyeux
      echo
      done

      最后再来验证我如下链路的流量:
      image.png

      # terminal 1
      export GRPC_PROVIDER_HOST=39.102.37.176
      java -jar consumer/target/consumer-1.0.0.jar
      
      # terminal 2
      for ((i=1;i<=10;i++)) ;  
      do   
      curl localhost:9001/bye
      echo
      done

      到此,GPRC服务成功部署到ASM。篇幅所限,更多服务网格的能力有待开发者结合自己的业务场景去探索。欢迎技术交流。

      ]]>
      进击的Kubernetes调度系统(一):Scheduling Framework Fri, 20 Jun 2025 02:20:33 +0800 作者:王庆璨 张凯

      前言

      Kubernetes已经成为目前事实标准上的容器集群管理平台。它为容器化应用提供了自动化部署、运维、资源调度等全生命周期管理功能。经过3年多的快速发展,Kubernetes在稳定性、扩展性和规模化方面都有了长足进步。 尤其是Kubernetes控制平面的核心组件日臻成熟。而作为决定容器能否在集群中运行的调度器Kube-scheduler,更是由于长久以来表现稳定,且已能满足大部分Pod调度场景,逐渐不被开发人员特别关注。

      伴随着Kubernetes在公有云以及企业内部IT系统中广泛应用,越来越多的开发人员尝试使用Kubernetes运行和管理Web应用和微服务以外的工作负载。典型场景包括机器学习和深度学习训练任务,高性能计算作业,基因计算工作流,甚至是传统的大数据处理任务。此外,Kubernetes集群所管理的资源类型也愈加丰富,不仅有GPU,TPU和FPGA,RDMA高性能网络,还有针对领域任务的各种定制加速器,比如各种AI芯片,NPU,视频编解码器等。开发人员希望在Kubernetes集群中能像使用CPU和内存那样简单地声明式使用各种异构设备。

      总的来说,围绕Kubernetes构建一个容器服务平台,统一管理各种新算力资源,弹性运行多种类型应用,最终把服务按需交付到各种运行环境(包括公共云、数据中心、边缘节点,甚至是终端设备),已然成为云原生技术的发展趋势。

      阿里云容器服务团队结合多年Kubernetes产品化与客户支持经验,对Kube-scheduler进行了大量扩展和改进,逐步使其在多种场景下依然能稳定、高效地调度复杂工作负载类型。

      《进击的Kubernetes调度系统》系列文章将把我们的经验、技术思考和实现细节全面地展现给Kubernetes用户和开发者,期望帮助大家更好地了解Kubernetes调度系统的强大能力和未来发展方向。

      早期方案

      首先,让我们来了解一下Kubernetes社区都有过哪些提升调度器扩展能力的方案。

      要统一管理和调度异构资源与更多复杂工作负载类型,首先面对挑战的就是Kube-scheduler。在Kubernetes社区里关于提升调度器扩展能力的讨论一直不断。sig-scheduling给出的判断是,越多功能加入,使得调度器代码量庞大,逻辑复杂,导致维护的难度越来越大,很多bug难以发现、处理。而对于使用了自定义调度的用户来说,跟上每一次调度器功能更新,都充满挑战。

      在阿里云,我们的用户遇到了同样的挑战。Kubernetes原生调度器循环处理单个Pod容器的固定逻辑,无法及时、简单地支持用户在不同场景的需求。所以针对特定的场景,我们会基于原生Kube-scheduler扩展自己的调度策略。

      最初对于Kube-scheduler进行扩展的方式主要有两种,一种是调度器扩展(Scheduler Extender), 另外一种是多调度器(Multiple schedulers)。接下来我们对这两种方式分别进行介绍和对比。

      Scheduler Extender

      社区最初提供的方案是通过Extender的形式来扩展scheduler。Extender是外部服务,支持Filter、Preempt、Prioritize和Bind的扩展,scheduler运行到相应阶段时,通过调用Extender注册的webhook来运行扩展的逻辑,影响调度流程中各阶段的决策结果。


      以Filter阶段举例,执行过程会经过2个阶段:

      1. scheduler会先执行内置的Filter策略,如果执行失败的话,会直接标识Pod调度失败。
      2. 如何内置的Filter策略执行成功的话,scheduler通过Http调用Extender注册的webhook, 将调度所需要的Pod和Node的信息发送到到Extender,根据返回filter结果,作为最终结果。

      scheduler-extender.svg

      我们可以发现Extender存在以下问题:

      1. 调用Extender的接口是HTTP请求,受到网络环境的影响,性能远低于本地的函数调用。同时每次调用都需要将Pod和Node的信息进行marshaling和unmarshalling的操作,会进一步降低性能。
      2. 用户可以扩展的点比较有限,位置比较固定,无法支持灵活的扩展,例如只能在执行完默认的Filter策略后才能调用。

      基于以上介绍,Extender的方式在集群规模较小,调度效率要求不高的情况下,是一个灵活可用的扩展方案,但是在正常生产环境的大型集群中,Extender无法支持高吞吐量,性能较差。

      Multiple schedulers

      Scheduler在Kubernetes集群中其实类似于一个特殊的Controller,通过监听Pod和Node的信息,给Pod挑选最佳的节点,更新Pod的spec.NodeName的信息来将调度结果同步到节点。所以对于部分有特殊的调度需求的用户,有些开发者通过自研Custom Scheduler来完成以上的流程,然后通过和default scheduler同时部署的方式,来支持自己特殊的调度需求。


      1588144283202-b3734bbe-b06d-4c65-81b2-d17bf44b521e.svg
      ]]> 进击的Kubernetes调度系统(二):支持批任务的Coscheduling/Gang scheduling Fri, 20 Jun 2025 02:20:33 +0800 作者:王庆璨 张凯

      进击的Kubernetes调度系统(一):Scheduling Framework
      进击的Kubernetes调度系统(二):支持批任务的Coscheduling/Gang scheduling

      前言

      首先我们来了解一下什么是Coscheduling和Gang scheduling。Wikipedia对 Coscheduling的定义是“在并发系统中将多个相关联的进程调度到不同处理器上同时运行的策略”。在Coscheduling的场景中,最主要的原则是保证所有相关联的进程能够同时启动。防止部分进程的异常,导致整个关联进程组的阻塞。这种导致阻塞的部分异常进程,称之为“碎片(fragement)”。
      在Coscheduling的具体实现过程中,根据是否允许“碎片”存在,可以细分为Explicit Coscheduling,Local Coscheduling和Implicit Coscheduling。 其中Explicit Coscheduling就是大家常听到的Gang Scheduling。Gang Scheduling要求完全不允许有“碎片”存在, 也就是“All or Nothing”。

      我们将上述定义的概念对应到Kubernetes中,就可以理解Kubernetes调度系统支持批任务Coscheduling的含义了。 一个批任务(关联进程组)包括了N个Pod(进程),Kubernetes调度器负责将这N个Pod调度到M个节点(处理器)上同时运行。如果这个批任务需要部分Pod同时启动即可运行,我们称需启动Pod的最小数量为min-available。特别地,当min-available=N时,批任务要求满足Gang Scheduling。

      为什么Kubernetes调度系统需要Coscheduling?

      Kubernetes目前已经广泛的应用于在线服务编排,为了提升集群的的利用率和运行效率,我们希望将Kubernetes作为一个统一的管理平台来管理在线服务和离线作业。默认的调度器是以Pod为调度单元进行依次调度,不会考虑Pod之间的相互关系。但是很多数据计算类的离线作业具有组合调度的特点,即要求所有的子任务都能够成功创建后,整个作业才能正常运行。如果只有部分子任务启动的话,启动的子任务将持续等待剩余的子任务被调度。这正是Gang Scheduling的场景。

      如下图所示,JobA需要4个Pod同时启动,才能正常运行。Kube-scheduler依次调度3个Pod并创建成功。到第4个Pod时,集群资源不足,则JobA的3个Pod处于空等的状态。但是它们已经占用了部分资源,如果第4个Pod不能及时启动的话,整个JobA无法成功运行,更糟糕的是导致集群资源浪费。


      1585658750819-8fe7f3da-ffc1-49a4-a5a0-9d8f7b624b59.png
      ]]> 在Kubernetes中用Alluxio加速Spark数据访问(一) Fri, 20 Jun 2025 02:20:33 +0800 1.背景信息

      1.1 alluxio

      Alluxio是一个开源的基于内存的分布式存储系统,适合作为云上大数据和AI / ML的数据编排方案。Alluxio可以同时管理多个底层文件系统,将不同的文件系统统一在同一个名称空间下,让上层客户端可以自由访问统一名称空间内的不同路径,不同存储系统的数据。

      alluxio的short-circuit功能可以使alluxio客户端直接访问alluxio worker所在主机的工作存储,而不需要通过网络栈与alluxio worker完成通信,可以提高性能。

      1.2 spark operator

      Spark-operator用于管理k8s集群中spark job。通过spark-operator可以在k8s集群中创建、查看和删除spark job。

      2.前提条件

      本文档的操作依赖如下的一些条件:

      • kubernetes集群:版本大于1.8,本次实验的集群通过阿里云容器服务创建,集群名称为"ack-create-by-openapi-1"。

      image.png

      • 安装有linux或者mac操作系统的计算机作为我们的实验环境(本次实验中,假设该计算机名称为alluxio-test)。该计算机需要准备如下环境:

        • docker >= 17.06
        • kubectl >= 1.8,能够连接kubernets集群ack-create-by-openapi-1

      3.实验步骤

      实验步骤主要包括如下几步:

      • 部署alluxio
      • 部署spark-operator
      • 制作spark docker镜像
      • 上传文件到alluxio
      • 提交spark job

      下面将对每个步骤进行说明:

      3.1 部署alluxio

      进入容器服务应用目录,在右上角的搜索框中搜索"alluxio",然后进入alluxio主界面,如图:
      image.png

      选择“参数”,修改配置中properties部分的"alluxio.user.short.circuit.enabled"值为"false",然后选择将alluxio安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),最后点击创建,如图
      image.png

      点击创建后,使用kubectl给待安装的alluxio组件的节点打上标签"alluxio=true",首先查看该集群有哪些节点:

      $ kubectl get nodes -o wide
      NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
      cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

      可以看到有三个worker节点,分别为:

      • cn-beijing.192.168.8.15
      • cn-beijing.192.168.8.16
      • cn-beijing.192.168.8.17

      我们给是三个节点都打上标签"alluxio=true":

      $ kubectl label nodes cn-beijing.192.168.8.15 
        cn-beijing.192.168.8.16 
        cn-beijing.192.168.8.17 
        alluxio=true

      使用kubectl查看各个pod是否都处于running状态:

      $ kubectl get po -n alluxio
      NAME                   READY   STATUS    RESTARTS   AGE
      alluxio-master-0       2/2     Running   0          4h1m
      alluxio-worker-5zg26   2/2     Running   0          4h1m
      alluxio-worker-ckmr9   2/2     Running   0          4h1m
      alluxio-worker-dvgvd   2/2     Running   0          4h1m

      验证alluxio是否处于ready:

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fsadmin report capacity
      
      Capacity information for all workers:
          Total Capacity: 3072.00MB
              Tier: MEM  Size: 3072.00MB
          Used Capacity: 0B
              Tier: MEM  Size: 0B
          Used Percentage: 0%
          Free Percentage: 100%
      
      Worker Name      Last Heartbeat   Storage       MEM
      192.168.8.15    0                capacity      1024.00MB
                                        used          0B (0%)
      192.168.8.16    0                capacity      1024.00MB
                                        used          0B (0%)
      192.168.8.17    0                capacity      1024.00MB
                                        used          0B (0%)

      3.2 部署spark-operator

      进入容器服务应用目录,在右上角的搜索框中搜索"ack-spark-operator",然后进入ack-spark-operator主界面,如图:
      image.png
      选择将ack-spark-operator安装到目标集群上(本次实验的集群为"ack-create-by-openapi-1"),然后点击创建,如图:
      image.png

      本次实验将会使用sparkctl向k8s集群提交一个spark job,需要将sparkctl安装到我们在"2.前提条件"中所提到的实验环境"alluxio-test"中:

      $ wget http://spark-on-k8s.oss-cn-beijing.aliyuncs.com/sparkctl/sparkctl-linux-amd64 -O /usr/local/bin/sparkctl
      $ chmod +x /usr/local/bin/sparkctl

      3.3 制作spark docker镜像

      spark下载页面下载所需的spark版本,本次实验选择的saprk版本为2.4.6。运行如下命令下载spark:

      $ cd /root
      $ wget https://mirror.bit.edu.cn/apache/spark/spark-2.4.6/spark-2.4.6-bin-hadoop2.7.tgz
      #

      下载完成后,执行解压操作:

      $ tar -xf spark-2.4.6-bin-hadoop2.7.tgz
      $ export SPARK_HOME=/root/spark-2.4.6-bin-hadoop2.7

      spark docker镜像是我们提交spark任务时使用到的镜像,这个镜像中需要包含alluxio client jar包。使用如下的命令获取alluxio client jar包:

      $ id=$(docker create alluxio/alluxio-enterprise:2.2.1-1.4)
      $ docker cp $id:/opt/alluxio/client/alluxio-enterprise-2.2.1-1.4-client.jar 
          $SPARK_HOME/jars/alluxio-enterprise-2.2.1-1.4-client.jar
      $ docker rm -v $id 1>/dev/null

      alluxio client jar包准备好以后,开始构建镜像:

      $ docker build -t spark-alluxio:2.4.6 -f kubernetes/dockerfiles/spark/Dockerfile $SPARK_HOME

      请记住镜像名称“spark-alluxio:2.4.6”,在向k8s提交spark job中会用到这个信息。

      镜像构建完成以后,对镜像的处理有两种方式:

      • 如果有私有镜像仓库,将该镜像推送到私有镜像仓库中,同时保证k8s集群节点能够pull该镜像
      • 如果没有私有镜像仓库,那么需要使用docker save命令将该镜像导出,然后scp到k8s集群的各个节点,在每个节点上使用docker load命令将镜像导入,这样就能保证每个节点上都存在该镜像。

      3.4 上传文件到alluxio

      文章开头提到过:本次实验是提交一个spark job到k8s中,该spark job的目标是对某一个文件统计每一个单词出现的次数。现在需要把这个文件传到alluxio存储上,这里为了方便,直接把alluxio master中/opt/alluxio-2.3.0-SNAPSHOT/LICENSE(文件路径可能因alluxio版本有点差异)这个文件传到alluxio上。

      使用"kubectl exec"进入alluxio master pod,并拷贝当前目录下的LICENSE文件到alluxio的根目录中:

      $ kubectl exec -ti alluxio-master-0  -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fs copyFromLocal LICENSE /

      接着查看一下LICENSE这个文件分成的block被alluxio放到哪些worker上了。

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      
      bash-4.4# alluxio fs stat /LICENSE
      /LICENSE is a file path.
      FileInfo{fileId=33554431, fileIdentifier=null, name=LICENSE, path=/LICENSE, ufsPath=/opt/alluxio-2.3.0-SNAPSHOT/underFSStorage/LICENSE, length=27040, blockSizeBytes=67108864, creationTimeMs=1592381889733, completed=true, folder=false, pinned=false, pinnedlocation=[], cacheable=true, persisted=false, blockIds=[16777216], inMemoryPercentage=100, lastModificationTimesMs=1592381890390, ttl=-1, lastAccessTimesMs=1592381890390, ttlAction=DELETE, owner=root, group=root, mode=420, persistenceState=TO_BE_PERSISTED, mountPoint=false, replicationMax=-1, replicationMin=0, fileBlockInfos=[FileBlockInfo{blockInfo=BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}, offset=0, ufsLocations=[]}], mountId=1, inAlluxioPercentage=100, ufsFingerprint=, acl=user::rw-,group::r--,other::r--, defaultAcl=}
      Containing the following blocks:
      BlockInfo{id=16777216, length=27040, locations=[BlockLocation{workerId=8217561227881498090, address=WorkerNetAddress{host=192.168.8.17, containerHost=, rpcPort=29999, dataPort=29999, webPort=30000, domainSocketPath=, tieredIdentity=TieredIdentity(node=192.168.8.17, rack=null)}, tierAlias=MEM, mediumType=MEM}]}

      可以看到LICENSE这个文件只有一个block(id为16777216),被放在了ip为192.168.8.17的k8s节点上。我们使用kubectl查看该节点名称为cn-beijing.192.168.8.17

      $ kubectl get nodes -o wide
      NAME                      STATUS   ROLES    AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                               KERNEL-VERSION            CONTAINER-RUNTIME
      cn-beijing.192.168.8.12   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.12   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.13   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.13   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.14   Ready    master   21d   v1.16.6-aliyun.1   192.168.8.14   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.15   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.15   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.16   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.16   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5
      cn-beijing.192.168.8.17   Ready    <none>   21d   v1.16.6-aliyun.1   192.168.8.17   <none>        Aliyun Linux 2.1903 (Hunting Beagle)   4.19.57-15.1.al7.x86_64   docker://19.3.5

      3.5 提交spark job

      下面的步骤将提交一个spark job到k8s集群中,该job主要是计算alluxio中/LICENSE文件的每个单词出现的次数。

      在步骤3.4中我们获取到LICENSE这个文件所包含的block都在节点cn-beijing.192.168.8.17上,此次实验中,我们通过指定node selector让spark driver和spark executor都运行在节点cn-beijing.192.168.8.17,验证在关闭alluxio的short-circuit功能的情况下,spark executor和alluxio worker之间的通信是否通过网络栈完成。

      • 说明:如果在开启alluxio的short-circuit功能的情况下,并且spark executor与其所要访问的文件(本次实验为/LICENSE这个文件)的block在同一个k8s节点上,那么spark executor中的alluxio client与该k8s节点上的alluxio worker之间的通信通过domain socket方式完成。

      首先生成提交spark job的yaml文件:

      $ export SPARK_ALLUXIO_IMAGE=<步骤3.3中制作的image,即spark-alluxio:2.4.6>
      $ export ALLUXIO_MASTER="alluxio-master-0"
      $ export TARGET_NODE=<步骤3.4获取到的LICENSE文件的block存储的节点,即cn-beijing.192.168.8.17>
      $ cat > /tmp/spark-example.yaml <<- EOF
      apiVersion: "sparkoperator.k8s.io/v1beta2"
      kind: SparkApplication
      metadata:
        name: spark-count-words
        namespace: default
      spec:
        type: Scala
        mode: cluster
        image: "$SPARK_ALLUXIO_IMAGE"
        imagePullPolicy: Always
        mainClass: org.apache.spark.examples.JavaWordCount
        mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.6.jar"
        arguments:
          - alluxio://${ALLUXIO_MASTER}.alluxio:19998/LICENSE
        sparkVersion: "2.4.5"
        restartPolicy:
          type: Never
        volumes:
          - name: "test-volume"
            hostPath:
              path: "/tmp"
              type: Directory
        driver:
          cores: 1
          coreLimit: "1200m"
          memory: "512m"
          labels:
            version: 2.4.5
          serviceAccount: spark
          volumeMounts:
            - name: "test-volume"
              mountPath: "/tmp"
          nodeSelector:
            kubernetes.io/hostname: "$TARGET_NODE"
        executor:
          cores: 1
          instances: 1
          memory: "512m"
          labels:
            version: 2.4.5
          nodeSelector:
            kubernetes.io/hostname: "$TARGET_NODE"
          volumeMounts:
            - name: "test-volume"
              mountPath: "/tmp"
      EOF

      然后,使用sparkctl提交spark job:

      $ sparkctl create /tmp/spark-example.yaml

      4.实验结果

      当提交任务后,使用kubectl查看spark driver的日志:

      $ kubectl get po -l spark-role=driver
      NAME                                 READY   STATUS      RESTARTS   AGE
      spark-alluxio-1592296972094-driver   0/1     Completed   0          4h33m
      
      $ kubectl logs spark-alluxio-1592296972094-driver --tail 20
      
      USE,: 3
      Patents: 2
      d): 1
      comment: 1
      executed: 1
      replaced: 1
      mechanical: 1
      20/06/16 13:14:28 INFO SparkUI: Stopped Spark web UI at http://spark-alluxio-1592313250782-driver-svc.default.svc:4040
      20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend: Shutting down all executors
      20/06/16 13:14:28 INFO KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint: Asking each executor to shut down
      20/06/16 13:14:28 WARN ExecutorPodsWatchSnapshotSource: Kubernetes client has been closed (this is expected if the application is shutting down.)
      20/06/16 13:14:28 INFO MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped!
      20/06/16 13:14:28 INFO MemoryStore: MemoryStore cleared
      20/06/16 13:14:28 INFO BlockManager: BlockManager stopped
      20/06/16 13:14:28 INFO BlockManagerMaster: BlockManagerMaster stopped
      20/06/16 13:14:28 INFO OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped!
      20/06/16 13:14:28 INFO SparkContext: Successfully stopped SparkContext
      20/06/16 13:14:28 INFO ShutdownHookManager: Shutdown hook called
      20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /var/data/spark-2f619243-59b2-4258-ba5e-69b8491123a6/spark-3d70294a-291a-423a-b034-8fc779244f40
      20/06/16 13:14:28 INFO ShutdownHookManager: Deleting directory /tmp/spark-054883b4-15d3-43ee-94c3-5810a8a6cdc7

      最后我们登陆到alluxio master上,查看相关指标统计到的值:

      $ kubectl exec -ti alluxio-master-0 -n alluxio bash
      //下面步骤alluxio-master-0 pod中执行
      bash-4.4# alluxio fsadmin report metrics
      Cluster.BytesReadAlluxio  (Type: COUNTER, Value: 290.47KB)
      Cluster.BytesReadAlluxioThroughput  (Type: GAUGE, Value: 22.34KB/MIN)
      Cluster.BytesReadDomain  (Type: COUNTER, Value: 0B)
      Cluster.BytesReadDomainThroughput  (Type: GAUGE, Value: 0B/MIN)

      BytesReadAlluxio和BytesReadAlluxioThroughput代表数据从网络栈传输;BytesReadDomain和BytesReadDomainThroughput代表数据从domain socket传输。可以看到所有数据都是从网络栈传输的(即使spark executor和LICENSE文件的block在同一k8s节点上)。

      5.参考文档

      ]]>
      【漏洞预警】Apache Spark 远程代码执行漏洞(CVE-2020-9480) Fri, 20 Jun 2025 02:20:33 +0800

      2020年6月24日,阿里云应急响应中心监测到 CVE-2020-9480 Apache Spark 远程代码执行漏洞。


      漏洞描述

      Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。由于Apache Spark的认证机制存在缺陷,导致共享密钥认证失效。攻击者利用该漏洞,可在未授权的情况下,远程发送精心构造的过程调用指令,启动Spark集群上的应用程序资源,获得目标服务器的权限,实现远程代码执行。阿里云应急响应中心提醒Apache Spark 用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      Apache Spark< = 2.4.5


      安全版本

      Apache Spark 2.4.6 或 3.0以上版本


      安全建议

      建议将Apache Spark升级至安全版本。下载地址参考:https://spark.apache.org/downloads.html


      相关链接

      https://spark.apache.org/security.html#CVE-2020-9480



      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。

      阿里云应急响应中心

      2020.06.24

      ]]>
      【漏洞预警】Apache Dubbo反序列化漏洞CVE-2020-1948补丁被绕过 Fri, 20 Jun 2025 02:20:33 +0800

      2020年6月29日,阿里云应急响应中心监测到Apache Dubbo GitHub官方发布Pull requests修复了CVE-2020-1948漏洞补丁被绕过现象,Dubbo <=2.7.7版本仍存在反序列化漏洞。目前官方还未发布新版本,漏洞属0day级,风险极大。


      时间线

      2020年6月23日,阿里云发布【漏洞预警】Apache Dubbo反序列化漏洞(CVE-2020-1948)

      2020年6月29日,阿里云监测到CVE-2020-1948漏洞补丁被绕过,风险依旧存在。


      漏洞描述

      Apache Dubbo是一款应用广泛的Java RPC分布式服务框架。Apache Dubbo于2020年6月23日披露在Dubbo Provider中存在一个反序列化远程代码执行漏洞(CVE-2020-1948),官方发布2.7.7版本修复漏洞,但近日该漏洞补丁被绕过,经阿里云工程师测试绕过有效,且目前官方还未发布新版本,漏洞属0day级,风险极大。阿里云应急响应中心提醒Apache Dubbo用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      Apache Dubbo <=2.7.7


      安全版本

      暂无安全版本


      安全建议

      1. 禁止将Dubbo服务端端口开放给公网,或仅仅只对能够连接至Dubbo服务端的可信消费端IP开放。

      2. Dubbo协议默认采用Hessian作为序列化反序列化方式,该反序列化方式存在反序列化漏洞。在不影响业务的情况下,建议更换协议以及反序列化方式。具体更换方法可参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-protocol.html


      相关链接

      https://github.com/apache/dubbo/pull/6374


      阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。


      阿里云应急响应中心

      2020.06.29

      ]]>
      【其他】关于CDN产品新增突发带宽保障服务的通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【CDN突发峰值保障服务】【发布通知】

      • 为了给您提供更稳定、可靠的CDN服务,阿里云推出“突发峰值保障服务”,并更新【CDN产品服务协议】,具体如下:

      • 如果您对CDN服务有突发性的带宽使用需求(通常指带宽计费客户的全国使用增量超过500Gbps或流量计费客户的总量超过10Gbps,或者以上客户的突发增长的部分是平时平均使用量的30%),您需提前至少3个工作日(重大节日的突发,包括但不限于春晚,双十一等,需要提前至少1个月)联系阿里云申请突发增长的带宽用量,并确认能否/是否购买“突发峰值保障服务”,若购买成功的,可确保您的服务不受影响;若未购买“突发峰值保障服务”的,总量超过200Gbps(带宽计费)或10Gbps(流量计费)的,阿里云有权采取限流等措施来保障全网用户的稳定性(以上数值另有约定的,从其约定)。

      • 具体影响:

      1. 流量客户(峰值>10Gbps,或>合同约定值)

      2. 带宽客户(峰值>200Gbps,或>合同约定值)

      • 在触发带宽上限后会产生告警,阿里云可能会采取限流保护措施。

      • 温馨提示:

      1. 针对流量计费的客户,若有更大带宽的需求,可以转为带宽计费,或通过工单申请合理的带宽上限,由阿里云评估通过后给予上限调整;

      2. 所有客户可以通过申请“突发峰值保障服务”来确保突发带宽的服务质量


      • 阿里云计算有限公司

      ]]>
      【升级】6月22日阿里云商标服务升级维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【阿里云商标服务】【升级维护通知】

      升级窗口:北京时间2020年6月22日 00:00 - 08:00

      升级内容:因业务系统升级,阿里云商标服务将于上述时间停服维护

      升级影响:升级期间,登录及查看阿里云商标控制台、购买阿里云阿里云商标服务(含商标注册申请、驳回复审申请、续展申请及商标优选业务)、支付费用及补齐申请材料将停止服务,请您在维护结束后再进行操作。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】6月22日阿里云公司注册升级维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【公司注册】【升级维护通知】

      升级窗口:北京时间2020年6月22日 00:00 - 08:00

      升级内容:阿里云云上公司注册和公司注册服务将于上述时间进行系统升级维护。

      升级影响:升级期间,gs.aliyun.com的云上公司注册和公司注册服务将无法下单, 工商注册的控制台也将无法使用。如果您需要购买或管理以上业务操作,建议您避开该时间段,以免给您的业务造成影响。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】6月22日阿里云图片与设计服务升级维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【图片与设计服务】【升级维护通知】

      升级窗口:北京时间2020年6月22日 00:00 - 08:00

      升级内容:因业务系统升级,阿里云图片与设计服务将于上述时间停服维护。

      升级影响:升级期间,登录及查看阿里云的图片与设计控制台,购买阿里云图片与设计服务(含图片单张、图片VIP、Logo设计)、支付费用将暂停服务,请您在维护结束后再进行操作。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】6月22日阿里云资质管家/ICP/EDI咨询代理服务升级维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【资质管家/ICP/EDI咨询代理服务】【升级维护通知】

      升级窗口:北京时间2020年6月22日 00:00 - 08:00

      升级内容:因业务系统升级,阿里云资质管家 / 阿里云ICP证 / 阿里云EDI证咨询代理服务将于上述时间停服维护。

      升级影响:系统升级维护期间,登陆查询阿里云资质管家/工商财税控制台,提交购买阿里云资质相关服务,提交咨询单、需求单,补齐材料等操作将停止服务,请您在维护结束后再进行相关操作。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】6月22日阿里云云市场代理记账服务应用系统升级维护通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【云市场代理记账服务】【升级维护通知】

      升级窗口:北京时间2020年6月22日 00:00 - 08:00

      升级内容:因业务系统升级,云市场代理记账服务的应用系统将于上述时间停服维护。

      升级影响:升级期间,云市场代理记账服务的应用系统,包括登录及查看、补充代账企业信息、服务进度及报表查询将停止服务。请您在维护结束后再进行操作。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【升级】6月29日Datahub升级通知 Fri, 20 Jun 2025 02:20:33 +0800

      【阿里云】【Datahub】【升级通知】

      升级窗口:北京时间2020年6月29日 10:00 - 19:00

      升级区域:华北,华南,华东,新加坡,吉隆坡,孟买,法国

      升级内容:

      1、支持MaxComputer2.0数据类型同步

      2、支持订阅延迟云监控

      升级影响:升级期间,会出现短暂的请求失败重试,属于正常现象,请确保您在业务上做好重连重试机制,以增强业务的容错能力。

      给您带来的不便敬请谅解,有任何问题,可点击联系我们进行咨询反馈。

      ]]>
      【漏洞预警】Apache Dubbo反序列化漏洞(CVE-2020-1948) Fri, 20 Jun 2025 02:20:33 +0800

      2020年6月23日,阿里云应急响应中心监测到 CVE-2020-1948 Apache Dubbo反序列化漏洞。


      漏洞描述

      Apache Dubbo是一款应用广泛的Java RPC分布式服务框架。Apache Dubbo于2020年6月23日披露在Dubbo Provider中存在一个反序列化远程代码执行漏洞(CVE-2020-1948),攻击者可以构造并发送带有恶意参数负载的RPC请求,当恶意参数被反序列化时将导致远程代码执行。阿里云应急响应中心提醒Apache Dubbo用户尽快采取安全措施阻止漏洞攻击。


      影响版本

      Apache Dubbo 2.7.0 ~ 2.7.6

      Apache Dubbo 2.6.0 ~ 2.6.7

      Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)。


      安全版本

      Apache Dubbo >= 2.7.7


      安全建议

      升级至安全版本或禁止Dubbo Provider对外开放


      相关链接

      https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html

      https://github.com/apache/dubbo/releases/tag/dubbo-2.7.7


      阿里云云安全中心应急漏洞模块已支持对该漏洞一键检测


      我们会关注后续进展,请随时关注官方公告。

      如有任何问题,可随时通过工单或服务电话95187联系反馈。


      阿里云应急响应中心

      2020.06.23

      ]]>
      windows激活报错0x80070020或0x80041010-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      作者:棋玉

      问题现象

      激活报错,slmgr /ato后报错代码类似如下:
      image.png

      排查步骤

      1.运行命令行 slmgr /dlv 同样报错,说明是slmgr 本身命令有问题,不像是网络层面的问题

      2.查看msinfo32, 发现报错,提示 winmgmt 服务有问题
      image.png

      3.重启winmgmt 服务,可以正常重启,说明winmgmt 服务本身正常,需要rebuild wmi 数据库。
      image.png

      4.按照以下步骤rebuild wmi 数据库(注:此操作可能会对环境产生影响,建议先进行快照)
      windows Server 2008R2
      右击cmd,选择以管理员身份运行,运行以下命令行:

      sc config winmgmt start= disabled
      net stop winmgmt /y
      cd %windir%system32wbem
      rename repository repository.old
      for /f %s in ('dir /b *.dll') do regsvr32 /s %s
      wmiprvse /regserver 
      sc config winmgmt start= auto
      net start winmgmt
      for /f %s in ('dir /b *.mof *.mfl') do mofcomp %s

      Windows Server 2012及以后版本
      右击cmd,选择以管理员身份运行,运行以下命令行:

      sc config winmgmt start= disabled
      net stop winmgmt /y
      %systemdrive%
      cd %windir%system32wbem
      ren repository repository-backup
      for /f %s in ('dir /b *.dll') do regsvr32 /s %s
      sc config winmgmt start= Auto
      net start winmgmt
      dir /b *.mof *.mfl | findstr /v /i uninstall > moflist.txt & for /F %s in (moflist.txt) do mofcomp %s

      5.之后成功激活。

      ]]>
      Docker容器启动时初始化Mysql数据库-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 77.jpg
      镜像下载、域名解析、时间同步请点击 阿里巴巴开源镜像站

      1. 前言
      Docker在开发中使用的越来越多了,最近搞了一个Spring Boot应用,为了方便部署将Mysql也放在Docker中运行。那么怎么初始化 SQL脚本以及数据呢?
      我这里有两个传统方案。 第一种方案是在容器启动后手动导入,太low了不行。第二种在Spring Boot客户端连接Mysql容器时初始化数据库,你可以参考使用flyway进行数据库版本控制一文,但是这依赖客户端的能力。能不能做到Mysql容器启动时就自己初始化数据库呢?当然可以!今天就来演示一下。全部代码见文末。
      2.原理
      当Mysql容器首次启动时,会在 /docker-entrypoint-initdb.d目录下扫描 .sh,.sql,.sql.gz类型的文件。如果这些类型的文件存在,将执行它们来初始化一个数据库。这些文件会按照字母的顺序执行。默认情况下它们会初始化在启动容器时声明的 MYSQL_DATABASE变量定义的数据库中,例如下面的命令会初始化一个REGION_DB 数据库:
      image.png
      如果你的启动命令没有指定数据库那么就必须在数据库DDL脚本中声明并指定使用该数据库。否则就会实现下面的异常:
      image.png
      那么接下来我们将利用这一机制来实现Docker容器启动时初始化数据库。
      3.自定义Dockerfile
      我们编写自己的Dockerfile来实现我们的需求,这里以 Mysql:5.7 为例。不同的版本可能有一定的出入,需要详细去阅读官方文档。脚本如下:
      image.png

      • 第一步,引入官方 Mysql:5.7 Docker镜像。
      • 第二步,无实际意义,主要是作者、组织信息。
      • 第三步,很重要!本来我没有配置第三行,结果运行容器后发现初始化数据的中文全部乱码了。所以需要在初始化数据库前修改Mysql的编码等配置,这里我顺便把时区也改为了+8:00。
      • 第四步,复制包含数据库脚本的 ./sql文件夹到镜像的/tmp/sql下。
      • 第五步,使用 mv 命令把第四步拷贝的文件夹下的所有.sql文件复制到 /docker-entrypoint-initdb.d下,这样才能利用2.章节的机制进行初始化数据库。
      • 第六步,删除使用过的临时目录。

      然后你可以通过构建镜像命令构建自定义的Mysql镜像:
      image.png
      通过mysql:5.7c镜像启动一个名称为mysql-service的容器,root密码为123456,并持久化数据到宿主机 D:/mysql/data下:

      docker run --name mysql-service -v d:/mysql/data:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7c

      小贴士:你可以通过 SHOW VARIABLES LIKE 'character%' 查看字符集是否更改为utf8mb4,也可以通过SHOW VARIABLES LIKE '%time_zone%' 查看时区是否是东八区。
      4. 总结
      今天我们自定义一个可以执行初始化数据库的Mysql镜像,方便我们进行部署。你也可以参考这个思路来定制其它一些自己需要的Docker镜像。

      本文作者 | 码农小胖哥
      本文来自 | 掘金

      阿里巴巴开源镜像站 提供全面,高效和稳定的镜像下载服务。钉钉搜索 ' 21746399 ‘ 加入镜像站官方用户交流群。”

      ]]>
      2020年便宜购买阿里云服务器攻略(阿里云小站篇)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文主要讲通过阿里云小站便宜购买阿里云服务器的攻略,力争将可获取的优惠完整清晰的列出来,以供阿里云新老用户参考。云小站所提供的优惠包括代金券优惠、今日限时秒杀、新用户专享1折起、企业新用户特惠、心选建站以及其他热门活动推荐。

      地址:阿里云小站

      一:代金券优惠
      首先,有一张金额为20元的云服务器ECS代金券,虽然金额比较小,但是如果你是购买1核1G 1核2G这种配置比较低的阿里云服务器还是很有用的,这张20元的代金券是只要订单金额满100元即可使用,云小站上有一款1核2G5M带宽的云服务器,优惠价格是197.47元,使用这张代金券就可以抵扣20元,实际购买只要177.47元。
      要注意的是:20元的代金券只能是新用户才能领取,而且有效期是7天,因此,领取了之后请尽快使用哦。

      如果你需要购买的阿里云服务器配置比较高,那么这张20元的代金券作用就不是很大了,订单金额比较高的,就需要用到其他几张代金券了,另外几张可用于购买阿里云服务器使用的代金券金额分别是:100元云服务器专用代金券、300元云产品通用代金券、800元云产品通用代金券,小编已经领取过了,根据小编了解的政策,这几张代金券的使用订单限制是:100元的订单满1000元可使用,300元的订单满3000元可使用,800元的订单满8000元可使用。
      需要注意的是:这几张代金券的有效期是30天,我们可以领取了以后使用,但是这几张代金券仅限产品新购才能用,如果我们是老用户,想用这个代金券去续费阿里云服务器使用,是用不了的。
      代金券.png

      二、今日限时秒杀
      限时秒杀所推出的这几款阿里云服务器,小编觉得是最经济实惠的,具体配置和秒杀价额如下下图所示:
      限时秒杀.png
      如果你是个人用户,放的网站流量不是很高,也没有在线视频这些吃带宽比较高的内容,推荐购买秒杀价格只要320.20元的这款共享型s6实例2核4G1M带宽的秒杀云服务器。如果您是企业用户,对云服务器性能要求高一点,推荐购买秒杀价格为636.72元一年的这款配置为共享型s6实例4核8G5M带宽的云服务器,这个配置可以满足绝大部分企业用户的上云需求了。

      三、新用户专享1折起
      新用户1折起专区所展示的优惠云产品,主要汇集了当下阿里云其他优惠活动中所推出的一些爆款云产品及配置,也就是用户购买最多的一些阿里云服务器配置和其他云产品,目前已经上架了27款云产品,除了阿里云服务器之外,还有云数据库MySQL高可用版、国内通用短信套餐包、云数据库SQL Server、SSL证书等云产品。

      小编比较推荐的是活动价格为1166.40元起,实例规格为计算型c5的这款2核4G1M带宽的阿里云服务器,放一些主流的网站是没问题的,比如企业官网之类的。
      具体云服务器产品及价格如下表:

      实例规格 配置 带宽 云小站报价
      共享型 s6 2核8G 2M 460.80元/1年 1209.60元/3年
      共享型 s6 4核8G 5M 1910.16元/3年
      通用型 g5 2核8G 1-5M可选 1576.80元/1年起
      通用型 g6 2核8G 1-5M可选 1495.80元/1年起
      通用型 g6 4核16G 1-5M可选 2791.80元/1年起
      计算型 c6 2核4G 1-5M可选 1209.60元/1年起
      计算型 c6 4核8G 1-5M可选 2219.40元/1年起
      计算型 c5 2核4G 1M 1166.40元/1年
      计算型 c5 4核8G 5M 2954.88元/1年

      四、企业新用户特惠
      企业新用户特惠是阿里云最近才在云小站上推出的全新板块,折扣低至2.6折,只要是企业认证且初次购买ECS的新用户均可以购买此版块的云服务器。这个专区所推出的云服务器主要以计算型c5 计算型c6和通用型g5 通用型g6为主,配置包含了2核4G、4核8G、2核8G、4核16G等,这些配置都是企业用户最常购买的一些配置。
      另外,企业新用户特惠专区所推出的云服务器带宽均为1-10M带宽可选,这就给了企业用户在选择带宽上很大的灵活空间,因为企业用户的网站一般来说流量都比较高,最高10M足可以应付网站的流量高峰了。
      具体云服务器产品及价格如下表:

      实例规格 配置 带宽 云小站报价
      共享型 s6 2核4G 1-10M可选 301.44元/1年起
      共享型 s6 2核8G 1-10M可选 572.88元/1年起
      共享型 s6 4核8G 1-10M可选 731.28元/1年起
      计算型 c5 2核4G 1-10M可选 699.84元/1年起 2021.76元/3年起
      计算型 c5 4核8G 1-10M可选 1279.80元/1年起 3697.20元/3年起
      共享型 s6 2核4G 1-10M可选 904.32元/3年起
      共享型 s6 2核8G 1-10M可选 1484.28元/3年起
      共享型 s6 4核8G 1-10M可选 2094.12元/3年起
      通用型 g5 2核8G 1M 946.08元/1年
      计算型 c5 2核4G 1-10M可选 1166.40元/1年起 2954.88元/3年起
      计算型 c5 4核8G 1-10M可选 2133.00元/1年起 5403.60元/3年起
      计算型 c5 8核16G 1-10M可选 4066.20元/1年起
      计算型 c6 2核4G 1-10M可选 1209.60元/1年起
      计算型 c6 4核8G 1-10M可选 2219.40元/1年起
      计算型 c6 8核16G 1-10M可选 4239.00元/1年起
      通用型 g6 2核8G 1-10M可选 1495.80元/1年起 3789.36元/3年起
      通用型 g6 4核16G 1-10M可选 2791.80元/1年起
      通用型 g6 8核32G 1-10M可选 5383.80元/1年起
      通用型 g5 2核8G 1-10M可选 1576.80元/1年起
      通用型 g5 4核16G 1-10M可选 2953.80元/1年起
      通用型 g5 8核32G 1-10M可选 5707.80元/1年起

      五、爆款云产品5折起
      这个专区是阿里云专为老用户开设的优惠购买通道,虽然总体价格还是没有新用户那么便宜,但是提供优惠的产品还是比较丰富的,包括云服务器ECS、云数据库Mysql、CDN流量包、Web应用防火墙、短信套餐包,这些应该都是老用户经常用到的产品,另外还提供了商标注册、企业工商注册等服务。
      具体云服务器产品及价格如下表:

      实例规格 配置 带宽 云小站报价
      计算网络增强型sn1ne 2核4G 1M 2386.80元/1年
      计算网络增强型sn1ne 4核8G 1M 4396.20元/1年
      计算网络增强型sn1ne 8核16G 1M 8415.00元/1年
      内存网络增强型se1ne 2核16G 1M 3671.40元/1年
      内存网络增强型se1ne 4核32G 1M 6965.40元/1年
      内存网络增强型se1ne 8核64G 1M 13553.40元/1年
      通用网络增强型sn2ne 2核8G 1M 2951.40元/1年
      通用网络增强型sn2ne 4核16G 1M 5525.40元/1年
      通用网络增强型sn2ne 8核32G 1M 10673.40元/1年

      六、心选建站
      心选建站是阿里云最近上架的建站产品专区,产品包括模板站基础版、模板站标准版、模板站企业版、定制站标准版、定制站高级版、定制站尊贵版。目前主要的优惠为云·企业官网类建站产品可以享受买一年送半年,下单抽iphone11奖品的优惠。

      七、热门活动推荐
      这里汇总了阿里云全站所有热门的优惠活动,如果云小站上所展示的云产品或者配置不是自己想要的,你可以通过该板块了解阿里云当下正在进行哪些热门活动,通过其他热门活动去选购自己想要的阿里云服务器或者其他云产品。

      ]]>
      内存占用高-AWE-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      作者:棋玉

      问题现象:

      内存占用高,任务管理器看不到问题,使用RAMMAP, 看到AWE 占用了大部分内存
      https://docs.microsoft.com/en-us/sysinternals/downloads/rammap
      image.png

      解决方案:

      AWE 占用内存高的问题一般发生在SQL 服务器上,一般建议客户调整“ maximum server memory”
      https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/server-memory-server-configuration-options?view=sql-server-ver15

      如果服务器上只安装了SQL 应用,建议给系统留2-4G 内存,其他内存留给SQL。
      image.png

      可以参考如下指标:
      image.png

      ]]>
      二叉树前序、中序、后序遍历的非递归写法-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 前序和中序遍历的非递归写法都比较好实现,后序遍历稍微复杂一些.

      数据结构定义:

      struct Node{

      char c;
      pNode lchild, rchild;
      Node(char c, pNode lchild = nullptr, pNode rchild = nullptr) :

      c(c), lchild(lchild), rchild(rchild) {}

      };

      申请阿里云服务时,可以使用2000元阿里云代金券,阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui

      二叉树形态:

      A
      /
      B C
      / /
      D E F G

      /
      H I

      前序遍历:

      先根遍历,拿到一个节点的指针,先判断是否为空,不为空就先访问该结点,然后直接进栈,接着遍历左子树;为空则要从栈中弹出一个节点来,这个时候弹出的结点就是其父亲,然后访问其父亲的右子树,直到当前节点为空且栈为空时,算法结束.

      阿里云服务器1核2G低至82元/年,阿里云官活动网址:https://dashi.aliyun.com/site/yun/aliyun 可以用20代金券,即102-20=82。

      void preVisitStack(pNode root)
      {

      stack st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          visit(p);
          st.push(p);
          p = p->lchild;
      } else {
          p = st.top();
          st.pop();
          p = p->rchild;
      }

      }
      cout << endl;
      }

      中序遍历:

      和前序遍历一样,只不过在访问节点的时候顺序不一样,访问节点的时机是从栈中弹出元素时访问,如果从栈中弹出元素,就意味着当前节点父亲的左子树已经遍历完成,这时候访问父亲,就是中序遍历.

      void midVisitStack(pNode root)
      {

      stack st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          st.push(p);
          p = p->lchild;
      } else {
          p = st.top();
          visit(p);
          st.pop();
          p = p->rchild;
      }

      }
      cout << endl;
      }

      后序遍历:

      后续遍历就不一样了,首先肯定是先访问左子树,把父亲节点保存于栈中,问题是当元素从栈中弹出的时候,我们无法判断这个时候是该访问其右子树还是访问父亲节点,于是我们就需要一个标记,当访问左子树时我们把父亲节点的标记设为1,表示下一步如果弹出该节点,就访问其右子树;弹出一个节点时,我们要判断该节点的标记,如果是1,则访问其右子树,并把该节点的标记设置成2,表示下一步就访问该节点,然后把该节点继续入栈,如果是2,那么表示访问该节点,访问并且丢弃该节点.

      为了不继续添加新的数据结构,我是用了STL中的pair来封装节点与标记.

      void backVisitStack(pNode root)
      {

      stack > st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          st.push(make_pair(p, 1));
          p = p->lchild;
      } else {
          auto now = st.top();
          st.pop();
          if (now.second == 1) {
              st.push(make_pair(now.first, 2));
              p = now.first->rchild;
          } else
              visit(now.first);
      }

      }
      cout << endl;
      }

      完整测试代码:

      include
      using namespace std;

      typedef struct Node *pNode;

      struct Node{

      char c;
      pNode lchild, rchild;
      Node(char c, pNode lchild = nullptr, pNode rchild = nullptr) :

      c(c), lchild(lchild), rchild(rchild) {}

      };

      pNode build()
      {

      /*

           A
         /  
       B     C
      /    / 

      D E F G

          / 
         H   I

      */
      pNode root = new Node('A');
      root->lchild = new Node('B');
      root->rchild = new Node('C');
      root->lchild->lchild = new Node('D');
      root->lchild->rchild = new Node('E');
      root->rchild->lchild = new Node('F');
      root->rchild->rchild = new Node('G');
      root->rchild->lchild->lchild = new Node('H');
      root->rchild->lchild->rchild = new Node('I');
      return root;
      }

      void visit(pNode x)
      {

      cout << x->c << " ";
      }

      void preVisitStack(pNode root)
      {

      stack st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          visit(p);
          st.push(p);
          p = p->lchild;
      } else {
          p = st.top();
          st.pop();
          p = p->rchild;
      }

      }
      cout << endl;
      }

      void midVisitStack(pNode root)
      {

      stack st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          st.push(p);
          p = p->lchild;
      } else {
          p = st.top();
          visit(p);
          st.pop();
          p = p->rchild;
      }

      }
      cout << endl;
      }

      void backVisitStack(pNode root)
      {

      stack > st;
      pNode p = root;
      while (p || !st.empty()) {

      if (p) {
          st.push(make_pair(p, 1));
          p = p->lchild;
      } else {
          auto now = st.top();
          st.pop();
          if (now.second == 1) {
              st.push(make_pair(now.first, 2));
              p = now.first->rchild;
          } else
              visit(now.first);
      }

      }
      cout << endl;
      }

      int main()
      {

      pNode root = build();
      preVisitStack(root);
      midVisitStack(root);
      backVisitStack(root);
      }

      测试结果:

      依次为前序、中序、后序遍历的结果.

      A B D E C F H I G
      D B E A H F I C G
      D E B H I F G C A

      ]]>
      如何安装discuz论坛-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 安装discuz论坛的方法:

      首先,您需要去 http://www.discuz.net/ 下载一个合适的版本,并申请一个虚拟主机或服务器。建议你用阿里云服务器1核2G低至82元/年,阿里云官活动网址:https://dashi.aliyun.com/site/yun/aliyun 可以用20代金券,即102-20=82。
      然后通过FTP上传所需要的程序,
      运行安装程序,
      设置论坛

      安装详细流程

      第 1 步:FTP上传 (如果FTP软件也不会用,那就比较麻烦了。可以上百度搜索FTP教材)

      使用 FTP 软件登录您的服务器,建立一个单独的目录,或选择合适的位置,确保存放在此位置的文件能够被 web 请求所访问到,并且该目录中具有执行 PHP 代码的权限。将 Discuz! 文件包的 ./upload 目录中的全部文件和目录结构上传到服务器(注意是上传 upload 目录中的文件和目录,而不是上传包含 upload 目录本身的目录和结构)。

      如果您仍然不了解应该上传哪些内容,请参考《文件及目录结构》中的说明。

      申请阿里云服务时,可以使用2000元阿里云代金券,阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui

      Discuz! 要求使用 FTP 软件上传 php 文件时,使用二进制(BINARY)方式进行,否则将无法正常使用。有关二进制上传的具体细节,请参考《安装常见问题》中的说明。

      第 2 步:设置目录属性

      如果您的服务器使用 Windows 操作系统,可跳过这一步。

      您在正式安装以前,需要设置相关的目录属性,以便数据文件可以被 Discuz! 正确的读写。使用 FTP 软件登录您的服务器,将服务器上以下的目录属性设置为 777。

      ./templates
      ./templates/default
      ./templates/default/.
      ./attachments
      ./customavatar
      ./forumdata
      ./forumdata/cache
      ./forumdata/templates
      ./forumdata/threadcaches
      ./forumdata/logs
      如果您仍不了解那些目录或文件需要设置属性,请参考《文件及目录结构》中的说明。

      如果您不了解应该如何设置属性,请参考《安装常见问题》中的说明。

      第 3 步:配置数据库信息

      使用编辑器打开您本地机器上的默认配置文件(config.inc.php),看到以下的内容:

      $dbhost = 'localhost'; // database server

                  // 数据库服务器
      

      $dbuser = 'dbuser'; // database username

                  // 数据库用户名
      

      $dbpw = 'dbpw'; // database password

                  // 数据库密码
      

      $dbname = 'discuz'; // database name

                  // 数据库名
      

      $adminemail = 'admin@your.com'; // admin email

                  // 论坛系统 Email
      

      $dbreport = 0; // send db error report? 1=yes

                  // 是否发送数据库错误报告? 0=否, 1=是

      请依据以上的注释配置空间服务商提供的数据库服务器、用户名、密码及数据库名。如果您使用自己安装的服务器环境,我们建议您在可能的情况下,尽量不要使用 root 账号,而依据 Discuz! 及服务器上其他软件的需要,单独为每个程序分配账号和数据库,以减少安全问题发生的可能。

      请您了解:数据库参数我们也无法告诉您如何设置,需要根据服务器账号的实际情况而定。如果您不了解,请咨询您的空间服务商,他们会完整的告诉您具体的设置方法。

      配置好参数设置后,请保存该文件(config.inc.php),并不要忘记将其上传到服务器上的论坛目录中,覆盖原有的默认配置文件。

      第 4 步:执行安装脚本

      请在浏览器中运行 install.php,即访问 http://您的域名/论坛目录/install.php。

      安装脚本会检查您的服务器系统环境、剩余空间、数据库环境,并具备一定的纠错功能。如果您在之前某一步骤操作有问题,通常安装脚本会发现并作以提示,请您根据提示再对安装过程进行检查。如果没有提示出问题,请您按照其中的说明,完成最后的安装。使用中的问题,请参考《使用指南》。

      第 5 步:运行快速设置向导

      现在进行到最后一步,如果您了解整个论坛的设置则不必运行此向导。

      请在浏览器运行admincp.php,即访问 http://您的域名/论坛目录/admincp.php,进行设置,此向导将根据您论坛的类型批量设置相关参数,您可以在运行完后,再进入详细设置进行微调。

      安装成功后,请通过 FTP 删除安装脚本(install.php),以避免被再次安装。

      ]]>
      数据库周刊29│2020数据库研究报告;Oracle取消今年技术大会;腾讯云DBbridge发布支持一键迁库;饿了么迁至阿里云;PG数组查询;Oracle被比特币勒索;DM8 安全管理…-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 数据库周刊29.jpg

      热门资讯

      1、快讯:2020年Oracle OOW大会因疫情取消 系近20年首度
      【摘要】Oracle 近日宣布,2020年 Oracle OpenWorld 技术大会因新冠疫情而取消,取而代之的将是在线的免费网络分享活动,活动将在不久后宣布,敬请关注。Oracle OpenWorld原定今年秋天在拉斯维加斯举行,这是近20年内Oracle首次取消这一活动。

      2、腾讯云DBbridge正式发布:传统数据库迁移上云一键搞定
      【摘要】6月18日,腾讯云正式发布企业级数据库迁移产品DBbridge。这款产品通过提供一站式数据迁移平台以及专家服务,帮助企业实现异构数据库之间数据的迁移和同步。目前,DBbridge已经支持Oracle、TDSQL、TBase、MySQL、PostgreSQL等多种数据库类型的迁移。

      3、甲骨文第四财季收入同比下降6%,Cloud和License收入下降了22%
      【摘要】甲骨文今天发布了2020财年第四财季及全年财报。报告显示,甲骨文第四财季总营收为104.40亿美元,同比下降6%,Cloud和License收入下降了22%。Oracle首席执行官Safra Catz表示:因为疫情影响,业务交易量有所减少,随着各国重新开放经济,许多业务已经恢复,我们相信,大部分客户将继续购买使用我们的产品和服务。

      4、饿了么100%迁至阿里云:高峰时可支持1亿人同时点单
      【摘要】6月17日,饿了么宣布已完成100%上云,旗下所有业务系统、数据库设施等均已迁移至阿里云。高峰期饿了么可在阿里云上快速扩容,可以支持1亿人同时在线点单,低峰期则可以释放算力,每年节省上千万元,同时动态规划最优线路,让外卖更快送达。

      精选文章

      1、Postgresql array数组查询及操作array_remove、array_append
      【摘要】array_remove 从数组中移除某个值,array_append 向数组中批量插入值。移除数组中重复值:先将数组unnest,取distinct并再次转换成数组,如果得到的数组和name[1:cardinality(name)-1])相等,那么说明这些name数组中存在重复值,通过set name=name[1:cardinality(name)-1]达到去重目的。

      2、用Benchmark压测PostgreSQL测试
      【摘要】在我们学习数据库或者开发数据库监控软件的时候,经常需要给数据库加压。经过简单的配置,目前我们使用benchmark为Oracle、达梦、金仓、华为高斯、Postgres等数据库加压。本文分享使用Benchmark工具对PG库进行加压的实验案例。

      3、深入理解:Oracle Move 和Shrink释放高水位空间HWM的区别
      【摘要】在Oracle数据库中,进行了大量的数据增删改之后,就可能出现空间的碎片,通过 move 和shrink 可以对空间进行回收。本文介绍少了move 和shrink的功能介绍和注意事项。

      4、Oracle物化视图日志表收缩
      【摘要】某些物化视图日志由于客户端物化视图刷新地不是很及时,导致物化视图日志表膨胀地非常厉害,但实际日志表里的数据又不是很多。对于这种膨胀比较厉害的物化视图日志,需要进行收缩。本文分别介绍了线下和线下操作物化视图日志表收缩的方法。

      干货文档

      1、《2020数据库研究报告:国产数据库的宽赛道、高壁垒、新机遇》.doc
      【摘要】本文档为兴业证券发布的2020年国产数据库行业研究与投资机会报告。报告显示,数据库上云进入快车道,国产数据库有望实现弯道超车。当前国产数据库已经形成三大核心竞争阵营,包括:以阿里、腾讯和华为科技巨头为代表的云数据厂商……

      2、《数据安全:Oracle多场景下比特币勒索的解密与恢复实战》.ppt
      【摘要】近年来很多用户遭遇数据库的安全问题,比特币勒索也层出不穷。本文档带你了解Oracle数据库遭比特币勒索的场景与恢复手段,以及如何避免此类安全问题。

      3、《基于Oracle自治数据库快速开发智能分析应用》甲骨文-李青.doc
      【摘要】本文档分享Oracle自治数据仓库入门、ADW自动化管理、ADW自动化调优、ADW帮助DBA工作减负,自治数据仓库云的关键应用场景和案例分享等。

      4、《达梦数据库 DM8 安全管理》官方文档.doc
      【摘要】本文档主要介绍达梦数据库DM8的安全体系结构,各安全管理模块的功能、原理及相关的数据库管理员和用户如何利用这些安全管理功能对数据库或数据进行安全保护。


      详情及往期周刊请访问墨天轮数据库周刊专栏。
      https://www.modb.pro/topic/12711?06

      ]]>
      C盘空间占满排查方案-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      作者:棋玉

      C盘空间占满的问题,首先要明确一下,空间被占满就是说明有文件把空间给占用了,右键所有文件属性看到差距很大的原因,总结有如下两个:
      1,隐藏文件(包括pagefile.sys)
      2, 系统管理员没有权限访问的文件比如System volume information 这个文件夹

      排查方案如下:

      =========

      1.将隐藏文件设为可见。

      Computer>Local Disk(C)>View>Options>Change folder and search options, 显示隐藏文件。
      image.png

      2.默认情况下,System volumeinformation 这个文件夹系统管理员没有权限访问,所以大小为0.

      运行以下命令行赋予管理员完全控制权限这样才知道这个文件夹的实际大小。用管理员权限运行以下命令行:

      takeown /f "C:system volumeinformation" /r /a
      icacls "c:system volumeinformation" /grant builtinadministrators:F

      3.使用工具 windirstat 或者treesize工具,这两个工具都可以非常直观的显示每个目录及文件夹占用大小
      https://windirstat.net/
      image.png

      4.如果以上两个工具也未能定位到问题,说明还是存在管理员没有权限查看的文件,比如:
      IIS服务器: 主要是log 文件,默认是如下路径: C:inetpublogsLogFiles
      SQL server: 查看如下路径C:Program FilesMicrosoft SQLServerMSSQL10_50.MSSQLSERVERMSSQL

      PS:第三步中使用工具如果看到占用最多的是winsxs 或者任何系统目录的话,不要轻易去清除这个文件夹,而是应该查看除了这个文件夹以外哪些文件占用了磁盘空间。

      Winsxs 相当于系统的备份数据库,不可以完全删除或者移除,一般来讲清理的空间非常有限,如果坚持要清理,请参考如下链接:
      2008R2:
      https://support.microsoft.com/zh-cn/help/2852386/disk-cleanup-wizard-addon-lets-users-delete-outdated-windows-updates-o
      2012&2012R2:
      https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/dn251565(v=win.10)?redirectedfrom=MSDN

      ]]>
      多点生活在 Service Mesh 上的实践 | 线上直播整理-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      Service Mesh Webinar 是由 ServiceMesher 社区和 CNCF 联合发起的线上直播活动,活动将不定期举行,为大家带来 Service Mesh 领域的知识和实践分享。

      本文根据5月28日晚 Service Mesh Webinar#1 多点生活平台架构组研发工程师陈鹏,线上主题分享《多点生活在 Service Mesh 上的实践 -- Istio + Mosn 在 Dubbo 场景下的探索之路》整理,文末包含本次分享的视频回顾链接以及 PPT 下载地址。

      前言

      随着多点生活的业务发展,传统微服务架构的面临升级困难的问题。在云原生的环境下,Service Mesh 能给我们带来什么好处。如何使用社区解决方案兼容现有业务场景,落地成符合自己的 Service Mesh 成为一个难点。

      今天主要给大家分享一下 Service Mesh 的一些技术点以及多点生活在 Service Mesh 落地过程中适配 Dubbo 的一些探索。

      首先我们从三个方面入手:

      • 为什么需要 Service Mesh 改造;
      • 探索 Istio 技术点;
      • Dubbo 场景下的改造;

      为什么需要 Service Mesh 改造

      说到为什么需要改造,应该先说一下 Service Mesh 和传统微服务架构的一些特点。

      微服务

      微服务一般有这些模块:

      • 安全;
      • 配置中心;
      • 调用链监控;
      • 网关;
      • 监控告警;
      • 注册和发现;
      • 容错和限流;

      这些模块在传统的微服务架构中有的是和 SDK 结合在一起,有的是一个独立的中间件。

      特点:

      • 独立部署;
      • 模块的边界;
      • 技术多样性;

      正是由于技术多样性,我的微服务系统可以使用不同的语言进行开发,比如我一个商城系统,订单系统使用 Java 开发,库存系统使用 Go 开发,支付系统使用 Python 开发,微服务之间通过轻量级通信机制协作,比如:HTTP/GRPC 等。比如目前多点使用的 Dubbo(服务治理框架),随着多点生活的业务发展,目前遇到最棘手的问题就是中间件在升级过程中,推进很慢,需要业务方进行配合,接下来我们看看 Service Mesh。

      Service Mesh

      优点:

      • 统一的服务治理;
      • 服务治理和业务逻辑解藕;

      缺点:

      • 增加运维复杂度;
      • 引入延时;
      • 需要更多技术栈;

      看了 Service Mesh 的优缺点,如果我们 Mesh 化了之后就可以解决我们目前的痛点,升级中间件只需要重新发布一下 Sidecar 就好了,不同语言开发的微服务系统可以采用同样的服务治理逻辑,业务方就可以尝试更多的技术。

      探索 Istio 技术点

      在谈 Dubbo 场景下的改造之前我们先介绍一下 Istio 相关的技术点,然后结合 Dubbo 场景应该如何进行适配

      MCP

      MCP(Mesh Configuration Protocol)提供了一套用于订阅(Watch)、推送(Push)的 API,分为 Source 和 Sink 两个角色。

      • Source 是资源提供方(server),资源变化了之后推送给订阅者(Pilot),Istio 1.5 之前这个角色就是 Galley 或者自定义 MCP Server;
      • Sink 是资源的订阅者(client),在 Istio 1.5 之前这个角色就是 Pilot 和 Mixer,都是订阅 Galley 或者自定义 MCP Server 的资源

      MCP 的订阅、推送流程图:

      mcp

      为了和实际情况结合,我们就以 MCPServer 作为 Source,Pilot 作为 Sink 来介绍订阅、推送流程,其中 MCP 通信过程中所传输的「资源」就是 Istio 定义的 CRD 资源,如:VirtualService、DestinationRules 等。

      订阅

      • Pilot 启动后会读取 Configmap 的内容,里面有一个 configSources 的一个数组配置(Istio 1.5 之后没有这个配置,需要自己添加)、存放的是 MCP Server 的地址;
      • Pilot 连接 MCPServer 之后发送所关注的资源请求;
      • MCPServer 收到资源请求,检查请求的版本信息(可能为空),判断版本信息和当前最新维护的版本信息是否一致,不一致则触发 Push 操作,一致则不处理;
      • Pilot 收到 Push 数据,处理返回的数据(数据列表可能为空,为空也标示处理成功),根据处理结果返回 ACK(成功)/ NACK(失败),返回的应答中包含返回数据的版本信息,如果返回的是 NACK,Pilot 会继续请求当前资源;
      • MCPServer 收到 ACK(和资源请求一致)之后对比版本号,如果一致则不推送,否则继续推送最新数据;

      推送

      • MCPServer 自身数据发生变化,主动推送变化的资源给 Pilot;
      • Pilot 收到之后处理这些数据,并根据处理结果返回 ACK / NACK;
      • MCPServer 收到 ACK(和资源请求一致) 之后对比版本号,如果一致则不推送,否则继续推送最新数据;

      这样的订阅、推送流程就保证了 MCPServer 和 Pilot 资源的一致。MCPServer 只能通过 MCP 协议告诉 Pilot 资源发生变化了么?当然不是,MCPServer 可以使用创建 CR 的方式,Pilot 通过 Kubernetes 的 Informer 机制也能感知到资源发生变化了,只是通过 MCP 传输的资源在 Kubernetes 里面看不到,只是存在于 Pilot 的内存里面,当然也可以通过 Pilot 提供的 HTTP debug 接口(istiod_ip:8080/debug/configz)来查。

      https://github.com/champly/mcpserver  提供了一个 MCPServer 的一个 demo,如果需要更加细致的了解 MCP 原理可以看一看。

      更多 debug 接口可以查看: https://github.com/istio/istio/blob/5b926ddd5f0411aa50fa25c0a6f54178b758cec5/pilot/pkg/proxy/envoy/v2/debug.go#L103

      Pilot

      Pilot 负责网格中的流量管理以及控制面和数据面之前的配置下发,在 Istio 1.5 之后合并了 Galley、Citadel、Sidecar-Inject 和 Pilot 成为 Istiod。我们这里说的是之前 Pilot 的功能,源码里面 pilot-discovery 的内容。

      功能

      • 根据不同平台(Kubernetes、Console)获取一些资源,Kubernetes 中使用 Informer 机制获取 Node、Endpoint、Service、Pod 变化;
      • 根据用户的配置(CR、MCP 推送、文件)触发推送流程;
      • 启动 gRPC server 用于接受 Sidecar 的连接;

      推送流程

      • 记录变化的资源类型;
      • 根据变化的资源类型(数组)整理本地数据;
      • 根据变化的资源类型判断需要下发的 xDS 资源;
      • 构建 xDS 资源,通过 gRPC 下发到连接到当前 Pilot 的 Sidecar;

      xDS

      Sidecar 通过动态获取服务信息、对服务的发现 API 被称为 xDS。

      • 协议部分(ADS、控制资源下发的顺序及返回确认的数据);
      • 数据部分(CDS、EDS、LDS、RDS、SDS);

      Pilot 资源类型发生变化需要下发的 xDS 资源对照:

      资源名称 CDS EDS LDS RDS
      Virtualservices
      Gateways
      Serviceentries
      Destinationrules
      Envoyfilters
      Sidecars
      ConfigClientQuotaspecs
      ConfigClientQuotaspecbindings
      Authorizationpolicies
      Requestauthentications
      Peerauthentications
      Other

      以上内容是根据 源码 整理的

      MOSN

      MOSN 是一款使用 Go 语言开发的网络代理软件,作为云原生的网络数据平面,旨在为服务提供多协议、模块化、智能化、安全的代理能力。MOSN 是 Modular Open Smart Network 的简称。MOSN 可以与任何支持 xDS API 的 Service Mesh 集成,亦可以作为独立的四、七层负载均衡,API Gateway,云原生 Ingress 等使用。

      MOSN:https://github.com/mosn/mosn

      配置文件:

      • mosn_config:MOSN 的配置信息;
      • listener:LDS;
      • routers:RDS;
      • cluster:CDS 和 EDS;

      listener

      listener

      其中 address 就是 MOSN 监听的地址。

      filter chains

      filter_chains 在 MOSN 里面的 network chains,实现的还有:

      • fault_inject;
      • proxy;
      • tcp_proxy;

      network chains 同级的还有 listener chainsstream chains, 其中
      listener chains 目前只有 original_dst 实现。stream chains 可以对请求中的

      • StreamSender;
      • StreamReceiver;
      • StreamAccessLog;

      进行 BeforeRoute AfterRoute 这些关键步骤进行修改请求信息。

      所有的 filter 都只有两种返回结果:

      • Continue:如果后面还有 filter 那就执行后续 filter
      • Stop:执行完当前 filter 就不再继续执行了;
      conv

      看图中的配置信息 config 的内容, downstream_protocolupstream_protocol 这里如果配置不一致,就需要协议转换。比如 HTTP1 转换为 HTTP2,MOSN 就会先把 HTTP1 转换为 common 的中间协议,然后再把 common转换为 HTTP2,这样就实现了协议之间的转换。如果需要自己实现其他协议转换,那么只需要编写转换 common 的内容和 common 转换为当前协议的内容即可实现协议之间的互转。

      proxy

      我们再来看 filters 里面的 proxy,这个就是一个会经过路由的代理,配置信息里面配置了router_config_name,就是要路由的router名字。

      routers

      routers

      根据 listener 里面的 proxy 的配置信息里面的 router_config_name 会找到一个 router,如上图所示。然后就会根据请求里面的 domains 去匹配 virtual_hosts, 这里的 domains 里面在 HTTP 里面就会是 host,当在 Dubbo 协议里面我们可以把 service(有些地方叫做 interface、target,我们这里统一叫 service) 放到 x-mosn-host 这个 MOSN 的 Header 里面,MOSN 就可以根据这个去匹配 domains

      然后匹配到一个 virtual_hosts 之后,就会得到对应的 routers,这里又会根据 match 里面的匹配规则进行匹配,HTTP 协议里面可以根据 pathqueryparamheader 等信息进行匹配,具体匹配规则通过 VirtualService 下发,如果是 Dubbo 协议,那么可以套用 HTTPRoute 规则,然后把 Dubbo 的 attachment 解析出来当作 header去用,目前 MOSN 没有解析 attachment,我们自己实现了一个。

      匹配到了之后会得到一个 route,图中所示只有一个 cluster_name,如果是有多个 subset(DestinationRule 定义),那么就会有 weighted_cluster ,里面会有 cluster_nameweight 构成的对象的数组,例如:

      "route":{
          "weighted_clusters":[
              {
                  "cluster":{
                      "name":"outbound|20882|green|mosn.io.dubbo.DemoService.workload",
                      "weight":20
                  }
              },
              {
                  "cluster":{
                      "name":"outbound|20882|blue|mosn.io.dubbo.DemoService.workload",
                      "weight":80
                  }
              }
          ],
          "timeout":"0s",
          "retry_policy":{
              "retry_on":true,
              "retry_timeout":"3s",
              "num_retries":2
          }
      }

      其中 weight 之和必须为 100(Istio 定义的),必须是非负数的整数。

      下面有一些 timeoutretry_policy 服务策略。

      匹配上了之后会得到一个cluster_name,然后我们再看 cluster

      cluster

      routers 里面匹配出来的 cluster_name 作为 keycluster 里面会找到这么一个对象。

      cluster

      其中 lb_type 就是节点的负载均衡策略,目前 MOSN 支持:

      • ROUNDROBIN;
      • RANDOM;
      • WEIGHTED_ROUNDROBIN;
      • EAST_REQUEST;

      hosts 里面的 address 里面也可以配置权重,这个权重必须是大于 0 或小于 129 的整数。可以通过 Istio 1.6 里面的 WorkloadEntry 来配置权重。然后根据负载均衡策略拿到 host 之后直接请求到对应的节点。

      这就完成了流量的转发。接下来我们看看 Dubbo 场景下应该如何改造。

      Dubbo 场景下的改造

      所有的改造方案里面都是要把 SDK 轻量化,关于服务治理的逻辑下沉到 Sidecar,我们在探索的过程中有三种方案。

      Istio + Envoy

      这个方案是 Istio+Envoy 的方案,是参考的华为云的方案: https://support.huaweicloud.com/bestpractice-istio/istio_bestpractice_3005.html

      • 通过创建 EnvoyFilter 资源来给 xDS 资源打 patch;
      • Envoy 解析 Dubbo 协议中的 Service 和 Method;
      • 根据路由策略配置把流量转发到对应的 Provider;

      这种方案如果需要解析更多的 Dubbo 内容,可以通过 WASM 扩展。

      MOSN + Dubbo-go

      • MOSN 提供 Subscribe、Unsubscribe、Publish、Unpublish 的 HTTP 服务;
      • SDK 发送请求到 MOSN 提供的这些服务,让 MOSN 代为与真正的注册中心交互;
      • MOSN 通过 Dubbo-狗直接和注册中心连接;

      这种方案的话就不需要 Istio。

      Istio + MOSN

      这种方案就是我们现在采用的方案,包括:

      • 数据面改造;
      • 控制面适配;

      我们有一个理念就是如果能通过标准的 CRD 最好,如果描述不了的话我们就通过 EnvoyFilter 进行修改。这里特别说一下,我们一开始也有一个误区就是 EnvoyFilter 是作用于 Envoy,其实不是的,是对生成好的 xDS 资源进行 ADD, MERGE 等操作,目前只可以修改 LDS、RDS、CDS,这个修改也是有一定局限性的。如果 EnvoyFilter 修改不了某些特定的场景(比如 Istio 1.6 之前的 ServiceEntry 里面的 Endpoint 不能单独为每个实例指定不同的端口),那么我们只能修改 pilot-discovery 的代码,xDS 是不会作任何修改的。按照这个理念,我们开始探索如何改造。

      数据面改造

      mosn

      首先有三个端口需要说明一下:

      • 20880 : provider 监听端口;
      • 20881 : consumer 请求 mosn 的这个端口,mosn 做转发到 provider;
      • 20882 : 接受来自下游(mosn/consumer)的请求,直接转到 127.0.0.1:20880;

      步骤:

      • provider 启动之后请求本地 mosn 的注册接口,把服务信息注册到注册中心(zk/nacos),注册请求到达 mosn 之后,mosn 会把注册端口号改为 20882;
      • consumer 启动之后不需要连接注册中心,直接把请求发送到 127.0.0.1:20881;
      • consumer 端的 mosn 收到请求之后,根据配置信息 listener->routers->cluster->host,找到合适的 host(可以是 provider 的 mosn 或者 直接是 provider) 发送请求,这里的匹配过程可以修改 MOSN 让 Dubbo 的 service 作为 domains,attachment 作为 header;
      • provider 端 mosn 收到请求后(20882),直接转发请求到本地 127.0.0.1:20880;

      这个只是通过静态配置实现的,如果 provider 这些信息如何通过 Pilot 下发呢?

      控制面适配

      MOSN 本身支持 xDS API,配置信息可以通过 xDS 下发,而不是静态配置。我们有一个对接配置中心,注册中心的程序我们叫 Adapter,这个主要获取注册中心的服务信息,然后根据配置中心的服务治理策略(比如流程比例,还有一些我们内部的一些单元的信息)构建出 Istio 支持的 CR,然后创建 CR,Pilot 自己感知 CR 变化 或者 通过 MCP 把这些信息直接发送给 Pilot,触发 Pilot 的资源变化,然后 Pilot 根据资源的变化去下发一些 xDS 资源,Sidecar 收到资源变化后,就可以动态调整路由策略,从而达到服务治理的目的。

      最终架构图如图所示:

      architecture

      注册(灰色部分):

      1. provider 发送注册信息给 MOSN;
      2. MOSN 修改注册信息(端口号等),然后注册到真正到注册中心(ZK / Nacos 等);

      配置下发(蓝色部分):

      1. Adapter 连接注册中心和配置中心并感知其变化;
      2. Adapter 感知到变化之后通过 MCP 把变化的信息传递给 Pilot(或者创建 CR 让 Pilot 自己感知);
      3. Pilot 感知到资源变化触发配置下发流程,根据变化到资源类型下发对应到 xDS 资源到 连接到它的 Sidecar;

      服务请求(黄色部分):

      1. consumer 请求本地 127.0.0.1:20881(MOSN 监听的端口);
      2. MOSN 根据 listener->router->cluster 找到一个 host,然后把请求转发到这个 host 上;

      以上就完成了服务注册、发现、治理的所有逻辑。

      Istio 1.6 之后可以通过 WorkloadEntry + ServiceEntry 这两种 CRD 资源来描述集群外的服务,当实例上线或者下线的时候就会直接触发 EDS 增量下发

      Demo 演示

      首先要说明一下:

      • 由于没有真正的注册,所以使用手动添加 ServiceEntry 的方式代替 Adapter 功能;
      • Listener 和 Routers 配置信息目前是固定的;
      • Provider 只注册到本地 ZK;
      • Sidecar 注入到方式使用的是多个 Container;

      具体操作可以按照 mosn-tutorial,里面的istio-mosn-adapt-dubbo。即使你没有 Kubernetes 环境也可以尝试的,后期这个会移植到 MOSN 官网,敬请期待。

      mosn-tutorial:https://github.com/mosn/mosn-tutorial

      以上就是本期分享的全部内容,感谢大家的收看。

      本期嘉宾介绍

      陈鹏,多点生活平台架构组研发工程师,开源项目与云原生爱好者。有多年的网上商城、支付系统相关开发经验,2019年至今从事云原生和 Service Mesh 相关开发工作。

      回顾资料

      PPT 下载:https://github.com/servicemesher/meetup-slides/tree/master/2020/05/webinar
      视频回顾:https://www.bilibili.com/video/BV15k4y1r7n8

      ]]>
      通过阿里云云小站申请阿里云服务器低至89元/年-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 在阿里云云小站上购买阿里云服务器可以领取哪些优惠呢?申请阿里云服务时,可以使用2020元阿里云代金券,阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui

      云服务器ECS 20元代金卷, 云服务器ECS 100元代金券,阿里云通用代金券800元,云产品通用代金卷300元, 数据库代金券 500元, 数据库代金券 300元
      云服务器.png

      如上图 所示

      云服务器ECS 20元优惠券 用于阿里云新用户(从没买过阿里云任何产品的用户 域名不算)有效期7天。
      云服务器ECS 100元代金卷用于产品首购(没买过阿里云ecs的用户)有效期30天
      阿里云通用代金券800元和300元 产品首购(顾名思义涵盖所有阿里云产品首次购买可以使用)有效期30天
      阿里云数据库代金券500元(没有买过数据库的用户 可领取使用)有效期7天
      阿里云数据库代金券300元,产品复购(已经买过数据库的用户 可接着领取使用)
      我们领取代金券以后就可以挑选自己想要购买的云服务器机型如下图所示基本涵盖了各种基础应用场景。阿里云代金券阿里云官网领取网址:https://dashi.aliyun.com/site/yun/youhui
      QQ图片20200623134944.png

      更多阿里云云服务器优惠折扣可以去阿里云官网的:阿里云云小站 查看(https://dashi.aliyun.com/site/yun/youhui 看这个网址就知道属于阿里云官网,不是某个第三方网站 ),如果您有更多对活动不理解以及阿里云服务器技术上的问题可以联系我们,为您解答。

      ]]>
      Windows 数据恢复-动态盘显示无效-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800

      作者:棋玉

      背景:

      很多客户在使用磁盘时选择了动态盘,又对这块动态盘创建了快照,之后在数据恢复或其他场景下用快照创建了新的磁盘,将新磁盘挂载到相同实例时,发现新磁盘显示无效。
      image.png

      问题原因:

      动态盘通过LDM进行管理,对于mbr分区,LDM保存在磁盘的最后1MB(如下图所示),这1MB空间保存了磁盘信息,分区信息以及磁盘id,group id等,由于源磁盘和新磁盘最后1MB空间是一样的,两块盘的ldm数据库完全相同,对应的磁盘id,group id也是完全相同,导致系统只能识别一块磁盘。
      image.png

      可以通过微软的LDMDump工具查看LDM database的具体信息:
      https://docs.microsoft.com/en-us/sysinternals/downloads/ldmdump
      image.png

      解决方案:

      将新磁盘在无损数据的前提下从动态盘转换到基本盘:重新配置分区表并将system id从dynamic 改为ntfs。需要借助diskprobe工具 (包含在Windows XP Service Pack 2 Support Tools)https://www.microsoft.com/en-us/download/details.aspx?id=18546
      Drives找到对应的磁盘,选择Set Active(以drive2 为例)
      image.png

      首先读取sector0的信息,view 以partition table展示,其中relative 表示起始扇区,total sectors 表示总扇区数。从截图看到起始扇区是63,总扇区数是41940929,总扇区数=结束扇区-开始扇区+1,因此结束扇区是41940991。
      image.png

      image.png

      之后判断起始扇区和结束扇区是否正确,查看sector 63和 sector 41940991 都是空,说明起始和结束扇区不正确,需要在偏移位3的位置用ntfs 标志查找起始和结束扇区,分别是2048和41938943
      image.png

      image.png

      转到sector 2048,View 以NTFS BootSector 展示, Hidden sector 设置为起始扇区2048,Total sectors 设置为结束扇区-起始扇区=41936895
      image.png

      最后查看sector0, View 以Partition table 展示,relatvie 设置为起始扇区2048,Total Sectors 为结束扇区-起始扇区+1=41936896.
      image.png

      sector 0以Bytes显示,将42改为07(42表示的是动态分区,07表示是NTFS 分区 ),write sector 进行保存。
      image.png
      image.png

      重新扫描磁盘后,可以看到磁盘显示为一个基本盘,可以进行数据读取写入操作。
      image.png

      ]]>
      蚂蚁金服智能监控云原生可观测大盘设计概览-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 背景

      蚂蚁金服业务自定义监控是蚂蚁金服监控产品中的一个重要功能,主要是通过自定义日志数据源并配置大盘的方式来解决蚂蚁金服业务实时监控的需求。在产品功能上,用户可以通过对一系列日志数据源的创建、组织、管理及表单配置化的方式,简单、快速组织出一个多维监控大盘。这项能力在当时是一个具有创新性的能力,从功能到产品体验上很好解决了当时蚂蚁金服复杂业务监控的痛点。

      但是,随着蚂蚁金服监控产品的不断迭代更新,以及云原生可观测性对于监控大盘的高要求,大家对自定义监控的体验诉求也越来越多,包括更便捷的交互方式、更丰富的图表、更丰富的数据源、更多扩展点等,因此对监控大盘的升级也势在必行。

      本文将介绍蚂蚁金服监控产品在监控大盘方面的创新设计与尝试,新版自定义监控大盘 Barad-Dur 目标成为业界体验最优秀的自定义监控大盘,在交互、体验与设计理念上有诸多创新点,同时将以模块的形式发布,支持二次开发,可以同时为蚂蚁金服内外监控系统服务。

      产品体验

      WYSIWYG

      当前优秀的监控大盘产品都标配一个“所见即所得(WYSIWYG)”编辑器,这方面能力是蚂蚁金服监控产品一直缺失的。在蚂蚁金服监控产品中配置大盘还是通过传统的表单方式,对用户非常不友好、学习曲线陡峭、配置效率不高。导致用户经常将配置大盘作为一项需求提给监控团队,由监控团队的“大盘配置专家”来进行配置,不仅存在较高的沟通成本,也给监控团队增加了很大的负担。

      在新版监控大盘 Barad-Dur 中,对 WYSIWYG 编辑器的交互体验进行了大量工作,力求做到市面上最优秀的编辑体验。

      体验1:缩放

      Barad-Dur 的缩放是可以在四周以及四角上进行的,而市面上常见的大盘产品只支持右下角的缩放。由于坐标系统一般采用的是 (left, top, width, height) 来定义一个矩形,最容易实现的就是右下角缩放,只需要变动 width 和 height 两个参数即可。最难实现的是左上角的缩放,四个参数需要同时变动,且关系比较复杂。特别是在引入网格布局后,缩放时要自动“吸附”临近的网格点,难上加难。

      1_scale.gif

      体验2:拖动

      Barad-Dur 的图表拖动可以实现图表位置的一步交换,而市面上常见的大盘产品需要进行多次拖动才能实现两个图表的交换。且在拖动过程中,图表的整体布局会被打乱,而 Barad-Dur 不会存在这样的问题。

      2_swap.gif

      体验3:自动重布局

      Barad-Dur 的自动重布局功能非常强大,可以支持实时布局预览(当然市面上常见的大盘产品也支持),同时大盘的布局调整会根据具体操作(缩放、拖动)的方向进行。而市面上常见的大盘产品只能在垂直方向上进行布局调整,因为所用的算法非常简单,只是粗暴地把所有图表向页面上“推”。

      3_layout.gif

      体验4:任意位置

      Barad-Dur 的布局支持图表在任意位置摆放,而市面上常见的大盘产品由于上述的简陋算法,不支持此功能,所有的图表必须堆叠在页面的顶部。

      4_free.gif

      体验5:布局复位

      Barad-Dur 的自动重布局能够在对单个图表进行调整时将其他图表“推开”,然后更强大的是可以再将被推开的图表复位。这里找到了市面上常见的大盘产品直接拿来用的开源布局框架进行对比。该框架其实提供了上述的任意位置功能,然而由于没有布局复位的功能,导致该功能一旦启用,会令整个大盘在编辑过程中布局被扰乱,对用户起不到任何帮助,所以市面上常见的大盘产品没有启用这个功能。

      5_reset.gif

      体验6:文字编辑

      Barad-Dur 支持在大盘中添加静态文字以及对于文字的编辑。静态文字可用于公告、标题、说明等一些常见的大盘场景。

      6_text.gif

      功能对比

      Barad-Dur 市面上常见的大盘产品
      任意拖动 ✔︎ ✔︎
      任意缩放 ✔︎
      多样图表 ✔︎ ✔︎
      图表实时编辑 ✔︎ ✔︎
      图表导入导出 ✔︎ ✔︎
      任意布局 ✔︎
      添加文字 ✔︎

      综上对比,可以看出 Barad-Dur 的 WYSIWYG 编辑器在各项功能上已经领先于市面上常见的大盘产品。

      控制器

      大盘,即 Dashboard (in an automobile or similar vehicle) a panel beneath the front window having various gauges and accessories for the use of the driver; instrument panel。其本意是指汽车上的仪表板,这里的仪表板包括了两类组成部分:监视器、控制器。在仪表板上不仅能看到汽车的当前状态,也能对汽车进行各种控制。这是大盘的本意,但是就目前看来,市面上所有的监控大盘产品都缺失了控制器这个重要的组成部分,导致监控大盘其实只是监视大盘。如果只是用来监视的,那大盘独立存在就没有意义,就像汽车的仪表板上只有转速表、时速表、里程表,却没有油门、刹车、换挡杆。

      我们再来看几个工业产品的大盘:

      image.png

      面向普通消费者的量产产品

      image.png

      面向专业消费者的量产产品

      chernobyl.jpeg

      面向专家的定制产品

      控制器是不可或缺的组成部分,甚至比监视器更加重要。Barad-Dur 提供了在大盘中设置控制按钮的功能,可以实现一些简单的控制,比如关闭/启动报警、打开钉钉聊天窗口、启动控制预案等。日后会不断加入更加强大的控制功能,让蚂蚁金服监控大盘变成一个完整的监控系统。

      技术实现

      自定义数据源

      上文提到 Barad-Dur 支持二次开发,支持自定义数据源,仅需一点点工作即可接入自己的数据源:

      1. 继承 AbstractDatasource,并实现 doRequestData 接口;
      2. 调用 registerDatasource 将数据源注册至 Barad-Dur(如果使用 Barad-Dur 的数据源编辑器,可在注册时指定自定义的数据源的编辑器);

      Barad-Dur 会对所有的数据源进行包装,提供缓存、增量加载、请求合并等功能。

      统一时序数据源

      为了实现自定义数据源能够在任意图表中正确展现,Barad-Dur 定义了一种 universal 的时序数据格式,支持多 key 以及多 value。所有的时序数据源(以后可能会支持非时序数据源)都会将查询结果转换为这种格式,所有的图表也都会使用这种数据格式进行展现。

      使用统一数据格式的优势在于,图表和数据源都是按照同样的数据接口(约定)来实现的,所以图表和数据源是可以独立变化的。即图表可以任意切换而不需要改动数据源配置,数据源也可以任意切换而不需要调整图表配置。这是市面上常见的大盘产品做不到的。

      另一大优势在于计算。Barad-Dur 支持数据源的简单前端计算(如计算比率的场景需要将数据 A 与数据 B 相除),在使用了统一的数据格式之后,将计算也视为一个时序数据源,它的输入是一组时序数据源,也就是说一个计算数据源可以引用另一个计算数据源。这也是市面上常见的大盘产品做不到的。

      Scene Graph

      Scene Graph 的概念常用于游戏引擎对于场景的渲染。由于场景中各个节点有父子关系且子节点的空间关系常常用相对于父节点的量来表示,所以需要一种数据结构来将这些 local 空间的量(translation、rotation)转变为 global 空间的量,才能最终转换成屏幕空间的量用于渲染。这种父子关系恰好对应了大盘中的各个图表以及整个大盘的关系。就拿一个最常见的需求来举例说明:大盘上有全局回放的功能(这是一个非常重要的功能,没有这个功能大盘就对排查问题毫无意义),而每个图表又有自己的设置:

      • 时间跨度:分钟级的图表与秒级的图表不会展现同样范围的数据;
      • 时间偏移:图表数据产生存在不同的延时;

      我们可以使用类似 Scene Graph 的数据结构来保存每个图表自己的时间轴配置以及全局大盘的时间轴配置,最后计算出查询数据所需的时间参数。

      同时,未来还会引入技术栈的概念,即一个预定义的图表组,可以直接放入到自定义的大盘中,只需要做少量配置。例如,用户可以一步创建一台物理机的 CPU、Memory、Disk 监控图表,只需要修改这个图表组的 ip 参数。

      所以在 Barad-Dur 中借鉴了 Scene Graph 的设计理念,并融入了大盘的设计需求。

      image.png

      总体是一个树形结构,但是每个节点都会有一个 MVC 结构,将数据源、视图以及控制数据分离,控制流与数据流分离。同时数据源部分可以相互依赖,使 Barad-Dur 可以优化数据查询,做到缓存、增量查询、合并查询等。

      未来展望

      目前 Barad-Dur 已经内置支持 OpenTSDB、CeresDB(蚂蚁自研的高性能、分布式、高可靠时序数据库,支持  PromQL)以及部分蚂蚁金服内部数据源,计划将兼容更多数据源,如 PromQL、InfluxDB、MySQL 等常用监控数据源。本文提到的可以预定义一组图表以及一组变量,创建大盘时可以快速添加相应的图表组件,同时也支持导入从其他大盘产品直接导出的大盘,使用户可以快速平滑迁移。

      希望本文的介绍可以为大家在云原生监控领域的设计带来一些思考与启发,也欢迎关注该领域的优秀的你,跟我们交流更多想法~

      关于我们

      欢迎来到「蚂蚁智能运维」的世界。本公众号由蚂蚁智能监控团队出品,面向关注智能运维技术的同学,将不定期与大家分享云原生时代下蚂蚁金服在智能监控的架构设计与创新方面的思考与实践。

      蚂蚁智能监控团队,负责蚂蚁金服的基础设施和业务应用的监控需求,正在努力建设一个支撑百万级机器集群、亿万规模服务调用场景下的,覆盖指标、日志、性能和链路等监控数据,囊括采集、清洗、计算、存储乃至大盘展现、离线分析、告警覆盖和根因定位等功能,同时具备智能化 AIOps 能力的一站式、一体化的监控产品,并服务蚂蚁金服众多业务和场景。

      关于「智能运维」有任何想要交流、讨论的话题,欢迎留言告诉我们。

      PS:蚂蚁智能监控正在招聘 AIOps 专家,欢迎加入我们,有兴趣联系 boyan@antfin.com

      ]]>
      记Elasticsearch安装与数据同步-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800
    2. 公司开展电商业务,之前有开发过类似项目,并没有涉及首页搜索功能,这次鉴于首页所搜需求变动,不仅仅是搜索商品,还要搜索店铺,直播及主播,现有实现方式是直接搜索数据库,为了增加用户体验,减小数据库的压力,so改用Elasticsearch实现首页搜索功能。
    3. Elasticsearch安装

      1. 准备elasticsearch安装包,版本选择7.6.0;官网地址
      2. 使用root用户创建es用户
      useradd es

      然后为es用户设置密码

      passwd es
      
      1. 修改/etc/security/limits.conf文件 在文件末尾添加
      *       hard    nofile           65536
      *       soft     nofile           65536
      
      1. 修改/etc/security/limits.d/20-nproc.conf文件
      *            soft            nproc     4096
      
      *            hard          nproc     4096
      
      root       soft            nproc     unlimited
      

      在/etc/sysctl.conf文件末尾添加

      vm.max_map_count = 2621441

      root用户执行命令

      sudo sysctl -p /etc/sysctl.conf

      切换用户

      su es

      上传elasticsearch-7.6.0-linux-x86_64.tar.gz文件至es用户目录下

      解压到当前目录

      tar -zxvf elasticsearch-7.6.0-linux-x86_64.tar.gz

      修改/home/es/elasticsearch-7.6.0/config/elasticsearch.yml配置文件

      #这里写本机ip,
      network.host: 192.168.110.191
      #
      # Set a custom port for HTTP:
      #
      #http.port: 9200
      
      #跨域配置 网页直连使用需要该配置,否则不需要
      http.cors.enabled: true
      http.cors.allow-origin: "*"
      

      安装elasticsearch-analysis-ik中文分词器;在/home/es/elasticsearch-7.6.0/config/目录下创建analysis-ik目录

      cd /home/es/elasticsearch-7.6.0/config/
      mkdir analysis-ik

      将下载下来的中文分词器文件解压,并将config目录下的所有文件拷贝到/home/es/elasticsearch-7.6.0/config/analysis-ik目录下

      在/home/es/elasticsearch-7.6.0/plugins目录下创建analysis-ik目录

      cd /home/es/elasticsearch-7.6.0/plugins
      mkdir analysis-ik
      

      将分词器中除config之外的文件拷贝到/home/es/elasticsearch-7.6.0/plugins/analysis-ik目录下

      启动elasticsearch服务

      cd /home/es/elasticsearch-7.6.0/bin
      ./elasticsearch -d
      

      浏览器访问http://192.168.110.191:9200/出现如下页面则服务启动成功

      {
        "name" : "eshost",
        "cluster_name" : "elasticsearch",
        "cluster_uuid" : "muEaR2aHT6uT_F1lop84Lg",
        "version" : {
          "number" : "7.6.0",
          "build_flavor" : "default",
          "build_type" : "tar",
          "build_hash" : "7f634e9f44834fbc12724506cc1da681b0c3b1e3",
          "build_date" : "2020-02-06T00:09:00.449973Z",
          "build_snapshot" : false,
          "lucene_version" : "8.4.0",
          "minimum_wire_compatibility_version" : "6.8.0",
          "minimum_index_compatibility_version" : "6.0.0-beta1"
        },
        "tagline" : "You Know, for Search"
      }

      数据同步工具Logstash;注意:一定要保证版本一致

      cd /home/es/
      wget https://artifacts.elastic.co/downloads/logstash/logstash-7.6.0.zip
      unzip logstash-7.6.0.zip
      cd logstash-7.6.0
      mkdir myconfig
      cd myconfig

      在此目录下需要创建mysql.conf文件以及要执行的sql文件,以及数据库驱动jar包,示例(此示例为多表同步):

      -rw-rw-r--. 1 es es      23 Jun 22 14:08 commodity.sql
      -rw-rw-r--. 1 es es      24 Jun 23 10:35 instructor.sql
      -rw-rw-r--. 1 es es      18 Jun 23 10:36 live.sql
      -rw-rw-r--. 1 es es    4658 Jun 23 10:50 mysql.conf
      -rw-rw-r--. 1 es es 2018353 May 28 11:25 mysql-connector-java-8.0.9-rc.jar
      -rw-rw-r--. 1 es es      24 Jun 23 10:36 user_store.sql
      

      sql文件中的内容为

      select * from commodity

      mysql.conf文件中的内容为

      input {
          stdin {
          }
          jdbc {
            # mysql数据库连接
            jdbc_connection_string => "jdbc:mysql://192.168.110.191:3306/platform?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false"
            # mysqly用户名和密码
            jdbc_user => "xxx"
            jdbc_password => "xxxx"
            # 驱动配置
            jdbc_driver_library => "/home/es/logstash-7.6.0/myconfig/mysql-connector-java-8.0.9-rc.jar"
            # 驱动类名
            jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
            jdbc_paging_enabled => "true"
            jdbc_page_size => "50000"
            # 执行指定的sql文件
            statement_filepath => "/home/es/logstash-7.6.0/myconfig/commodity.sql"
            # 设置监听 各字段含义 分 时 天 月  年 ,默认全部为*代表含义:每分钟都更新
            schedule => "* * * * *"
            # 索引类型
            type => "commodity"
          }
              jdbc {
            # mysql数据库连接
            jdbc_connection_string => "jdbc:mysql://192.168.110.191:3306/platform?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false"
            # mysqly用户名和密码
            jdbc_user => "xxxx"
            jdbc_password => "xxxx"
            # 驱动配置
            jdbc_driver_library => "/home/es/logstash-7.6.0/myconfig/mysql-connector-java-8.0.9-rc.jar"
            # 驱动类名
            jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
            jdbc_paging_enabled => "true"
            jdbc_page_size => "50000"
            # 执行指定的sql文件
            statement_filepath => "/home/es/logstash-7.6.0/myconfig/instructor.sql"
            # 设置监听 各字段含义 分 时 天 月  年 ,默认全部为*代表含义:每分钟都更新
            schedule => "* * * * *"
            # 索引类型
            type => "instructor"
          }
              jdbc {
            # mysql数据库连接
            jdbc_connection_string => "jdbc:mysql://192.168.110.191:3306/platform?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false"
            # mysqly用户名和密码
            jdbc_user => "xxxx"
            jdbc_password => "xxxx"
            # 驱动配置
            jdbc_driver_library => "/home/es/logstash-7.6.0/myconfig/mysql-connector-java-8.0.9-rc.jar"
            # 驱动类名
            jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
            jdbc_paging_enabled => "true"
            jdbc_page_size => "50000"
            # 执行指定的sql文件
            statement_filepath => "/home/es/logstash-7.6.0/myconfig/live.sql"
            # 设置监听 各字段含义 分 时 天 月  年 ,默认全部为*代表含义:每分钟都更新
            schedule => "* * * * *"
            # 索引类型
            type => "live"
          }
              jdbc {
            # mysql数据库连接
            jdbc_connection_string => "jdbc:mysql://192.168.110.191:3306/platform?allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false"
            # mysqly用户名和密码
            jdbc_user => "xxxx"
            jdbc_password => "xxxx"
            # 驱动配置
            jdbc_driver_library => "/home/es/logstash-7.6.0/myconfig/mysql-connector-java-8.0.9-rc.jar"
            # 驱动类名
            jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
            jdbc_paging_enabled => "true"
            jdbc_page_size => "50000"
            # 执行指定的sql文件
            statement_filepath => "/home/es/logstash-7.6.0/myconfig/user_store.sql"
            # 设置监听 各字段含义 分 时 天 月  年 ,默认全部为*代表含义:每分钟都更新
            schedule => "* * * * *"
            # 索引类型
            type => "user_store"
          }
      }
      
      filter {
          json {
              source => "message"
              remove_field => ["message"]
          }
      }
      
      output {  
              if [type]=="user_store" {
                      elasticsearch {
                      #ESIP地址与端口
                              hosts => "192.168.110.191:9200"
                              #ES索引名称(自己定义的)
                              index => "user_store"
                              #自增ID编号
                              #document_id => "%{id}"
                      }
              }
              if [type]=="live" {
                      elasticsearch {
                      #ESIP地址与端口
                              hosts => "192.168.110.191:9200"
                              #ES索引名称(自己定义的)
                              index => "live"
                              #自增ID编号
                              #document_id => "%{id}"
                      }
              }
              if [type]=="instructor" {
                      elasticsearch {
                      #ESIP地址与端口
                              hosts => "192.168.110.191:9200"
                              #ES索引名称(自己定义的)
                              index => "instructor"
                              #自增ID编号
                              #document_id => "%{id}"
                      }
              }
              if [type]=="commodity" {
                      elasticsearch {
                      #ESIP地址与端口
                              hosts => "192.168.110.191:9200"
                              #ES索引名称(自己定义的)
                              index => "commodity"
                              #自增ID编号
                              #document_id => "%{id}"
                      }
              }
      }
      

      启动Logstash

      cd /home/es/logstash-7.6.0/bin
      ./logstash -f ../myconfig/mysql.conf &

      ]]> Elasticsearch集群模式知多少?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 本文不涉及实战操作,仅限集群原理范围探讨,基于版本7.6.X。

      作者介绍

      李猛,Elastic Stack 深度用户,通过 Elastic 工程师认证,2012年接触 Elasticsearch,对 Elastic Stack 技术栈开发、架构、运维等方面有深入体验,实践过多种大中型项目;为企业提供 Elastic Stack 咨询培训以及调优实施;多年实战经验,爱捣腾各种技术产品,擅长大数据,机器学习,系统架构。

      集群模式

      Elasticsearch节点设计支持多种角色,这个是实现集群最重要的前提,节点角色各司其职,也可以任意组合,职责重合。

      image.png

      节点角色说明:

      • Master,集群管理
      • Voting,投票选举节点
      • Data,数据节点
      • Ingest,数据编辑节点
      • Coordinate,协调节点
      • Machine Learning,集群学习节点

      以下展开各种集群模式

      1.单节点(入门)

      image.png

      单节点模式默认开启所有节点特性,具备一个集群所有节点角色,可以认为是一个进程内部的集群。Elasticsearch在默认情况下,不用任何牌配置也可以运行,这也是它设计的精妙之处,相比其它很多数据产品集群配置,如Mongodb,简化了很多,起步入门容易。

      2.基本高可用(初级)


      image.png

      Elasticsearch集群要达到基本高可用,一般要至少启动3个节点,3个节点互相连接,单个节点包括所有角色,其中任意节点停机集群依然可用。为什么要至少3个节点?因为集群选举算法奇数法则。

      3.数据与管理分离(中级)

      image.png

      Elasticserach管理节点职责是管理集群元数据、索引信息、节点信息等,自身不设计数据存储与查询,资源消耗低;相反数据节点是集群存储与查询的执行节点。

      管理节点与数据节点分离,各司其职,任意数据节点故障或者全部数据节点故障,集群仍可用;管理节点一般至少启动3个,任意节点停机,集群仍正常运行。

      4.数据与协调分离(高级)

      image.png

      Elasticsearch内部执行查询或者更新操作时,需要路由,默认所有节点都具备此职能,特别是大的查询时,协调节点需要分发查询命令到各个数据节点,查询后的数据需要在协调节点合并排序,这样原有数据节点代价很大,所以分离职责,详细技术原理可以观看腾讯课堂《Elastic技术原理:数据查询》,里面讲解了Elastic执行查询的过程。

      5.协调读写分离(高级)

      image.png

      Elasticsearch设置读写分离指的是在协调节点上,不是数据节点上,集群大量的查询需要消耗协调节点很大的内存与CPU合并结果,同时集群大量的数据写入会阻塞协调节点,所以在协调节点上做读写分离很少必要,也很简单,由集群设计搭建时规划好即可。

      6.数据节点标签(高级)

      image.png

      Elasticsearch给数据节点标签,目的是分离索引数据的分布,在一个集群规模中等以上,索引数据用途多种多样,对于数据节点的资源需求不一样,数据节点的配置可以差异化,有的数据节点配置高做实时数据查询,有的数据节点配置低做历史数据查询,有的数据节点做专门做索引重建。

      Elasticsearch集群部署时需要考虑基础硬件条件,集群规模越来越大,需要多个数据中心,多个网络服务、多个服务器机架,多个交换机等组成,索引数据的分布与这些基础硬件条件都密切相关。

      7.主副分片分离(高级)

      image.png

      Elasticsearch集群规模大了之后得考虑集群容灾,若某个机房出现故障,则可以迅速切换到另外的容灾机房。

      8.跨集群操作(高级)

      image.png

      Elasticsearch单个集群规模不能无限增长,理论上可以,实际很危险,通过创建多个分集群分解,集群直接建立直接连接,客户端可以通过一个代理集群访问任意集群,包括代理集群本身数据。

      Elasticsearch集群支持异地容灾,采用的是跨集群复制的机制,与同一集群主分片副本分片分离是不同的概念,2个集群完全是独立部署运行,仅数据同步复制。

      9.跨集群版本操作(高级)

      image.png

      Elasticsearch版本更新很快,已知问题修复很快,新特性新功能推出很快,一日不学,如隔三秋。有的集群数据重要性很高,稳定第一,不能随意升级,有的业务场景刚好需要最新版本新功能新特性支持。

      结语

      经验总结

      Elasticsearch集群模式种类挺多,每种集群模式都有它的应用场景以及解决实际的问题,每种集群模式之间也可以轻易转换。所以说ES玩的好,下班下得早。

      声明:本文由原文作者“李猛”授权转载,对未经许可擅自使用者,保留追究其法律责任的权利。


      image.png

      阿里云Elastic Stack】100%兼容开源ES,独有9大能力,提供免费X-pack服务(单节点价值$6000)

      相关活动


      更多折扣活动,请访问阿里云 Elasticsearch 官网

      阿里云 Elasticsearch 商业通用版,1核2G ,SSD 20G首月免费
      阿里云 Logstash 2核4G首月免费


      image.png

      image.png

      ]]>
      崩溃堆栈还原技术大揭秘-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 0x00 前言

      当应用出现崩溃的时候,程序员的第一反应肯定是:在我这好好的,肯定不是我的问题,不信我拿日志来定位一下,于是千辛万苦找出用户日志,符号表,提取出崩溃堆栈,拿命令开干,折腾好一个多小时,拿到了下面的结果:

      addr2line -ipfCe libxxx.so 007da904 007da9db 007d7895 00002605 007dbdf1
      logging::Logging::~Logging() LINE: logging.cc:856
      logging::ErrLogging::~ErrLogging() LINE: logging..cc:993
      base::internal::XXXX::Free(int) LINE: scoped____.cc:54
      base::___Generic<int, base::internal::_____loseTraits>::_____sary() LINE: scoped_______.h:153
      base::___Generic<int, base::internal::_____loseTraits>::_____eric() LINE: scoped_______.h:90


      如果是接入了岳鹰全景监控平台,场景就完全不一样了。测试同学:发来一个链接,附言研发哥哥,这是你的bug,请注意查收。研发哥哥:点开链接,就可以在平台看到这条崩溃信息啦,如下图:
      image.png


      那么问题来了,岳鹰上有这么多的应用版本,再加上海量的日志,对于Native崩溃,总不能每个崩溃点都用addr2line或者相关的命令去符号化吧?
      岳鹰的符号化系统正是为了解决该问题而设计。岳鹰最初上线的版本1.0,支持同时符号化解析数量有限,对iOS符号化时依赖Mac系统,不支持容器化部署,消耗机器资源较多。
      为了更好的满足用户业务需求,岳鹰在年初启动了2.0版本的改造,并且制定以下目标:

      • 同时解析不限数量的符号表
      • 提升符号化的效率
      • 解除Mac系统依赖,支持全容器化部署


      那这样一个分布式的符号化系统该如何设计呢?接下来小编就来详细介绍下。

      0x01 方案的选择

      结合当前系统设计以及业界常见方案,我们有以下几条路可以走:

      1. 岳鹰1.0方案,用大磁盘,高CPU性能的机器搭建符号化机器,符号文件存放到磁盘,需要符号化时再调用addr2line;
      2. 建立一个中央存储,把符号文件上传到中央存储,符号化机器需要符号化的时候再过去拉,然后用addr2line符号化;
      3. 把符号信息按key-value方式提取出来,存入hbase或者其它中间件,符号化时通过类sql查询实现。

      结合岳鹰2.0的目标,我们对三个方案进行对比:

      对比项 方案1 方案2 方案3
      符号表入库速度
      内存占用 常态高 常态高 入库时高
      CPU占用 常态高 常态高 入库时高
      安全性
      可扩展性
      部署复杂度


      方案1:符号文件上传倒是很快,如果需要高可用,还需要镜像一份到备机,且在做addr2line的时候,会带来高内存及高cpu的占用,而且不支持动态扩容,安全性也几乎没有,拿到机器就拿到了源码;

      方案2:符号文件存放于中央存储,做好备份机制后,能保障文件不会丢失,但机器在符号化时,都需要去中央存储拉符号文件,之后的处理同方案1,查询效率不高,而且安全性也不高;

      方案3:在符号入库时,把符号信息按key-value方式提取出来,然后加密存入hbase,这里要解决符号表全量导出及入库的速度及空间问题。

      结合岳鹰2.0目标,我们对日志处理的及时性,可扩展性,安全性,以及海量版本同时解析的要求,我们选择了方案3。下面我们先给大家简单介绍下原理,再深入看看选择方案3要解决哪些问题。

      0x02 原理(大神请忽略这一节)

      国际惯例,我们先来了解一下原理,符号表是什么?符号表是记录着地址或者混淆代码与源码的对应关系表。下面我们分别用一个小demo程序来讲解符号表及符号化的过程。

      0x02-1 iOS-OC、Android-SO符号化原理

      a.示例源码:

      int add(){
          int a = 1;
          a ++;
          int b = a+3;
          return b;
      }
      int div(){
          int a = 1;
          a ++;
          int b = a/0;                //这里除0会引发崩溃
          return b;
      }
      int _tmain(int argc, _TCHAR* argv[]){
          add();
          sub();
          return 0;
      }

      b.对应符号表,这里简化了符号表,没带行号信息

      0x00F913B0 ~ 0x00F913F0    add()
      0x00F91410 ~ 0x00F91450    div()
      0x00F91A90 ~ 0x00F91ACD    _tmain()

      c.现有一崩溃堆栈

      0x00F9143A
      0x00F91AB0

      d.进行符号化

      0x00F9143A    div()    //查找符号表,地址0x00F9143A的符号名,在0x00F91410 ~ 0x00F91450范围内
      0x00F91AB0    _tmain() //查找符号表,地址0x00F91AB0的符号名,在0x00F91A90 ~ 0x00F91ACD范围内

      0x02-2 Android-Java 符号化原理

      a.示例源码:

      package com.uc.meg.wpk
      class User{
          int count;
          UserDTO userDto;
          UserDTO get(int id){...}
          int set(UserDTO userDto){...} 
      }
      class UserDTO{
          int id;
          String name;
      }

      b.符号表

      com.a.b.c.d -> com.uc.meg.wpk.User
          int count -> a
          com.uc.meg.wpk.UserDTO -> b
          com.uc.meg.wpk.UserDTO get(int) -> c
          int set(com.uc.meg.wpk.UserDTO) -> d
      
      com.a.b.c.e -> com.uc.meg.wpk.UserDTO
          int id -> a
          String name -> b

      c.现有一崩溃堆栈

      com.a.b.c.d.d(com.a.b.c.e)

      d.进行符号化

      //符号化com.a.b.c.d.d(com.a.b.c.e)    
      //查找com.a.b.c.d, 命中com.uc.meg.wpk.User
      //查找com.uc.meg.wpk.User.d 命中 set()
      //查找com.a.b.c.e,命中 com.uc.meg.wpk.UserDTO
      //符号化结果为com.uc.meg.wpk.User.set(com.uc.meg.wpk.UserDTO) 

      0x03 新的难题

      选择方案3后,主要瓶颈在符号表上传之后处理,这里主要工作是要把符号表转换为key-value,然后再写入hbase。现在主流的app开发有android的java及C++,iOS的OC,我们下面主要讨论这三种符号。
      因为android的java符号化有google的开源工具支持,这里就不再展开。
      OC因为是iOS系统,封闭系统,标准统一,上架AppStrore的应用,只用XCode进行编译,没有各种定制的需求。我们原来有一个OC实现的符号表kv提取程序,但是只能用于OSX系统,不便于线上布署,所以我们选择了用java重写了提取符号kv的功能。
      但是对于Android的C++库so符号表,即ELF格式,存在着各种版本,各种定制下不同的编译参数,会大幅增加用java重写的成本,所以我们使用了Java跟C++结合的方式去实现ELF的符号表kv的提取,先用Java程序把ELF的基础信息,地址表读取出来,然后再用addr2line去遍历这个地址表,然后再把结果存入hbase,这个为100%的符号化成功率打下基础。

      0x03-1 addr2line的问题

      改进前后的对比

      改进前 改进后
      应用场景 十几个地址的符号化 批量的地址符号化
      QPS 50 800
      地址传递方式 参数,有限长度 文件,无限长度
      额外内存开销 1 0.7
      多任务模式 不支持 支持

      当然,这个addr2line,是要经过改造才能达到我们的要求,原来的addr2line是给开发者以单条命令去使用,不是给程序做批量查询的,每次查询都是要把整个ELF文件加载到内存,像UC内核,还有一些游戏的so文件,大小要到几百M的级别,每个addr2line进程都要一份独立的内存。假设一个500M的so符号,一台64核的机器,假如用60核去100%跑addr2line,加上其它开销,它就需要35G的内存。
      面对这么高的cpu和内存占用,而且是一个较低的QPS,单核大约100QPS,我们也尝试去优化addr2line的binutils中的bfd部分,但是最终的接口都是调用系统内核的,这条路,短期好像走不通。面对这样的性能问题,期间也多次尝试用Java去重写这部分逻辑,但是最终结果只能实现与addr2line的90%匹配度,而且还有很多未知的兼容性问题,最后还是选择了改造addr2line,改造点主要有以下三点:

      • 从文件读取地址表,使用批量请求去addr2line,减少bfd初始化的次数,因为这个过程中,bfd接口在调用一些特定的地址转换后,会导致qps降到个位数,需要重启进程才行;
      • 减少额外的内存开销;
      • 支持多进程,多容器分布式任务调度,支持动态扩缩容,提高资源利用率。

      改造后,单核的QPS大约提升到800QPS,上面举的500M的so符号的例子,大约需要15分钟,基本能满足我们的需求。

      0x03-2 存储的问题

      解决完提取的问题,接下来就是存储的问题。
      符号表都是经过精心设计的高度压缩的数据结构,我们通过上面的方案把它提取出kv的格式,容量上增加了10+倍,而且很多信息都是重复的,如函数名,文件名这些,虽然空间对于hbase来说不是什么问题,但是在追求极致的面前,我们还可以再折腾折腾。
      前面提到我们因为要考虑数据的安全性,需要把存入hbase的数据做加密,所以不能直接用hbase本身的压缩功能,要求在加密前先做好压缩,如果是按行压缩再加密,总体的压缩比不会太高,我们可以把00006740~000069eb这一段当成一个大段,把它们压缩在一起再加密,这样因为重复信息较多,压缩比会很高,最终的体积可以缩小5+倍,相当于只是比原始符号表大3~4倍。
      hbase rowkey的设计,因为后面的查询会需要用到scan,我们把符号表kv的结束地址作为rowkey的一部分,至于为什么这么设计,往下读,你就明白了。

      0x03-3 查询的问题

      根据0x01原理,对hbase的查询,需要get,scan的支持,get的话相对简单,直接通过rowkey命中就好了,适用于java符号化的场景,对于C++/OC的符号化,就需要scan的支持,因为地址是一个范围,不能用get直接命中,下面用伪代码举例说明scan的流程:

      //1. 扫描libxxx.so符号,地址范围0x00001234 ~ 0xffffffff, 只取一条结果
      //这里利用了scan的特性,我们存的rowkey是符号的结束地址,所以扫描出的第一个,
      //就是最接近0x00001234的一个符号
      raw = scan("libxxx.so", 0x00001234, 0xffffffff, limit=1);
      //2. 解密,解压,判断有效性预处理
      data = pre(raw);
      //3. 精确定位地址,根据0x04-2的打包存入,再做切割拆分
      result = splitData(data);


      旧系统我们只用了应用级的缓存,每次重启缓存就会丢失,为了减小hbase的压力,我们增加一级分布式缓存,使用redis作为缓存,进一步减少了末端的查询QPS。

      0x03-4 如果保证100%的符号化成功率

      我们知道,如果符号化失败,就会出现不一样的崩溃点,这样就不能把这些崩溃点聚合在一起,会把一些严重的问题分散掉,同时会产生很多新的崩溃点,导致开发,测试无法分辨真实的崩溃情况,我们使用以下技术保障成功率:

      • 高并发,低延迟的符号化查询服务,保障解析效率,防止超时出现符号化失败的情况;
      • 多级缓存保障,减少hbase的scan操作;
      • 使用原生addr2line提取符号kv;
      • 重试机制。

      0x04 总结

      0x04-1 符号化系统的核心能力

      通过几个平台的符号化反能力对比,我们可以看到岳鹰2.0取得的阶段性成果。

      对比项 方案1 方案2 方案3
      符号表入库速度
      内存占用 常态高 常态高 入库时高
      CPU占用 常态高 常态高 入库时高
      安全性
      可扩展性
      部署复杂度

      0x04-2 运行效果的提升

      指标 岳鹰1.0到2.0的提升
      CPU核心数 -50%
      平均CPU水位 -40%
      内存 持平
      符号入库速度(OC) +20%
      符号入库速度(Java) 持平
      符号入库速度(SO <= 100M) 持平
      符号入库速度(SO > 100M) 20分钟以内
      符号化响应速度 100ms -> 9ms
      容器化部署 全容器化



      0x05 欢迎免费试用

      岳鹰为阿里集团众多使用UC内核的app(如手淘,支付宝,天猫,钉钉,优酷等)提供内核so的崩溃符号化功能,实现了Java,Native C++的质量监控完整闭环,并在Native C++上的支持上明显优于其它竞品,开发者能快速地还原现场并找出问题,同时整个系统支持动态扩缩容,为更多业务接入打下了坚实的基础。
      更多功能,欢迎来岳鹰平台体验。

      ]]>
      一个秒杀系统的设计思考-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 秒杀大家都不陌生。自2011年首次出现以来,无论是双十一购物还是 12306 抢票,秒杀场景已随处可见。简单来说,秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程。从架构视角来看,秒杀系统本质是一个高性能、高一致、高可用的三高系统。而打造并维护一个超大流量的秒杀系统需要进行哪些关注,就是本文讨论的话题。

      整体思考

      首先从高维度出发,整体思考问题。秒杀无外乎解决两个核心问题,一是并发读,一是并发写,对应到架构设计,就是高可用、一致性和高性能的要求。关于秒杀系统的设计思考,本文即基于此 3 层依次推进,简述如下——

      高性能
      秒杀涉及高读和高写的支持,如何支撑高并发,如何抵抗高IOPS?核心优化理念其实是类似的:高读就尽量“少读"或“读少",高写就数据拆分。本文将从动静分离、热点优化以及服务端性能优化 3 个方面展开。
      一致性
      秒杀的核心关注是商品库存,有限的商品在同一时间被多个请求同时扣减,而且要保证准确性,显而易见是一个难题。如何做到既不多又不少?本文将从业界通用的几种减库存方案切入,讨论一致性设计的核心逻辑。
      高可用:
      大型分布式系统在实际运行过程中面对的工况是非常复杂的,业务流量的突增、依赖服务的不稳定、应用自身的瓶颈、物理资源的损坏等方方面面都会对系统的运行带来大大小小的的冲击。如何保障应用在复杂工况环境下还能高效稳定运行,如何预防和面对突发问题,系统设计时应该从哪些方面着手?本文将从架构落地的全景视角进行关注思考。

      高性能

      动静分离
      大家可能会注意到,秒杀过程中你是不需要刷新整个页面的,只有时间在不停跳动。这是因为一般都会对大流量的秒杀系统做系统的静态化改造,即数据意义上的动静分离。动静分离三步走:1、数据拆分;2、静态缓存;3、数据整合。

      **数据拆分
      动静分离的首**要目的是将动态页面改造成适合缓存的静态页面。因此第一步就是分离出动态数据,主要从以下 2 个方面进行:
      1.用户。用户身份信息包括登录状态以及登录画像等,相关要素可以单独拆分出来,通过动态请求进行获取;与之相关的广平推荐,如用户偏好、地域偏好等,同样可以通过异步方式进行加载
      2.时间。秒杀时间是由服务端统一管控的,可以通过动态请求进行获取这里你可以打开电商平台的一个秒杀页面,看看这个页面里都有哪些动静数据。

      静态缓存
      分离出动静态数据之后,第二步就是将静态数据进行合理的缓存,由此衍生出两个问题:1、怎么缓存;2、哪里缓存

      怎么缓存:
      静态化改造的一个特点是直接缓存整个 HTTP 连接而不是仅仅缓存静态数据,如此一来,Web 代理服务器根据请求 URL,可以直接取出对应的响应体然后直接返回,响应过程无需重组 HTTP 协议,也无需解析 HTTP 请求头。而作为缓存键,URL唯一化是必不可少的,只是对于商品系统,URL 天然是可以基于商品 ID 来进行唯一标识的,比如淘宝的 https://item.taobao.com/item.htm?id=xxxx

      哪里缓存:
      静态数据缓存到哪里呢?可以有三种方式:1、浏览器;2、CDN ;3、服务端。
      浏览器当然是第一选择,但用户的浏览器是不可控的,主要体现在如果用户不主动刷新,系统很难主动地把消息推送给用户(注意,当讨论静态数据时,潜台词是 “相对不变”,言外之意是 “可能会变”),如此可能会导致用户端在很长一段时间内看到的信息都是错误的。对于秒杀系统,保证缓存可以在秒级时间内失效是不可或缺的。
      服务端主要进行动态逻辑计算及加载,本身并不擅长处理大量连接,每个连接消耗内存较多,同时 Servlet 容器解析 HTTP 较慢,容易侵占逻辑计算资源;另外,静态数据下沉至此也会拉长请求路径。
      因此通常将静态数据缓存在 CDN,其本身更擅长处理大并发的静态文件请求,既可以做到主动失效,又离用户尽可能近,同时规避 Java 语言层面的弱点。需要注意的是,上 CDN 有以下几个问题需要解决:
      1.失效问题。任何一个缓存都应该是有时效的,尤其对于一个秒杀场景。所以,系统需要保证全国各地的 CDN 在秒级时间内失效掉缓存信息,这实际对 CDN 的失效系统要求是很高的
      2.命中率问题。高命中是缓存系统最为核心的性能要求,不然缓存就失去了意义。如果将数据放到全国各地的 CDN ,势必会导致请求命中同一个缓存的可能性降低,那么命中率就成为一个问题
      因此,将数据放到全国所有的 CDN 节点是不太现实的,失效问题、命中率问题都会面临比较大的挑战。更为可行的做法是选择若干 CDN 节点进行静态化改造,节点的选取通常需要满足以下几个条件:
      临近访问量集中的地区
      距离主站较远的地区
      节点与主站间网络质量良好的地区
      基于以上因素,选择 CDN 的二级缓存比较合适,因为二级缓存数量偏少,容量也更大,访问量相对集中,这样就可以较好解决缓存的失效问题以及命中率问题,是当前比较理想的一种 CDN 化方案。部署方式如下图所示:
      图片1.png

      数据整合
      分离出动静态数据之后,前端如何组织数据页就是一个新的问题,主要在于动态数据的加载处理,通常有两种方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。
      ESI 方案:Web 代理服务器上请求动态数据,并将动态数据插入到静态页面中,用户看到页面时已经是一个完整的页面。这种方式对服务端性能要求高,但用户体验较好。
      CSI 方案:Web 代理服务器上只返回静态页面,前端单独发起一个异步 JS 请求动态数据。这种方式对服务端性能友好,但用户体验稍差。

      小结
      动静分离对于性能的提升,抽象起来只有两点,一是数据要尽量少,以便减少没必要的请求,二是路径要尽量短,以便提高单次请求的效率。具体方法其实就是基于这个大方向进行的。
      **
      热点优化**
      热点分为热点操作和热点数据,以下分开进行讨论。
      热点操作
      零点刷新、零点下单、零点添加购物车等都属于热点操作。热点操作是用户的行为,不好改变,但可以做一些限制保护,比如用户频繁刷新页面时进行提示阻断。

      热点数据
      热点数据的处理三步走,一是热点识别,二是热点隔离,三是热点优化。

      1、热点识别
      热点数据分为静态热点和动态热点,具体如下:
      1.静态热点:能够提前预测的热点数据。大促前夕,可以根据大促的行业特点、活动商家等纬度信息分析出热点商品,或者通过卖家报名的方式提前筛选;另外,还可以通过技术手段提前预测,例如对买家每天访问的商品进行大数据计算,然后统计出 TOP N 的商品,即可视为热点商品
      2.动态热点:无法提前预测的热点数据。冷热数据往往是随实际业务场景发生交替变化的,尤其是如今直播卖货模式的兴起——带货商临时做一个广告,就有可能导致一件商品在短时间内被大量购买。由于此类商品日常访问较少,即使在缓存系统中一段时间后也会被逐出或过期掉,甚至在 DB 中也是冷数据。瞬时流量的涌入,往往导致缓存被击穿,请求直接到达 DB,引发 DB 压力过大

      因此秒杀系统需要实现热点数据的动态发现能力,一个常见的实现思路是:
      1.异步采集交易链路各个环节的热点 Key 信息,如 Nginx 采集访问 URL 或 Agent 采集热点日志(一些中间件本身已具备热点发现能力),提前识别潜在的热点数据
      2.聚合分析热点数据,达到一定规则的热点数据,通过订阅分发推送到链路系统,各系统根据自身需求决定如何处理热点数据,或限流或缓存,从而实现热点保护

      需要注意的是:
      1.热点数据采集最好采用异步方式,一方面不会影响业务的核心交易链路,一方面可以保证采集方式的通用性
      2.热点发现最好做到秒级实时,这样动态发现才有意义,实际上也是对核心节点的数据采集和分析能力提出了较高的要求

      2、热点隔离
      热点数据识别出来之后,第一原则就是将热点数据隔离出来,不要让 1% 影响到另外的 99%,可以基于以下几个层次实现热点隔离:
      1.业务隔离。秒杀作为一种营销活动,卖家需要单独报名,从技术上来说,系统可以提前对已知热点做缓存预热
      2.系统隔离。系统隔离是运行时隔离,通过分组部署和另外 99% 进行分离,另外秒杀也可以申请单独的域名,入口层就让请求落到不同的集群中
      3.数据隔离。秒杀数据作为热点数据,可以启用单独的缓存集群或者 DB 服务组,从而更好的实现横向或纵向能力扩展
      当然,实现隔离还有很多种办法。比如,可以按照用户来区分,为不同的用户分配不同的 Cookie,入口层路由到不同的服务接口中;再比如,域名保持一致,但后端调用不同的服务接口;又或者在数据层给数据打标进行区分等等,这些措施的目的都是把已经识别的热点请求和普通请求区分开来。

      3、热点优化
      热点数据隔离之后,也就方便对这 1% 的请求做针对性的优化,方式无外乎两种:
      1.缓存:热点缓存是最为有效的办法。如果热点数据做了动静分离,那么可以长期缓存静态数据
      2.限流:流量限制更多是一种保护机制。需要注意的是,各服务要时刻关注请求是否触发限流并及时进行review

      4、小结
      数据的热点优化与动静分离是不一样的,热点优化是基于二八原则对数据进行了纵向拆分,以便进行针对性地处理。热点识别和隔离不仅对“秒杀”这个场景有意义,对其他的高性能分布式系统也非常有参考价值。

      系统优化
      对于一个软件系统,提高性能可以有很多种手段,如提升硬件水平、调优JVM 性能,这里主要关注代码层面的性能优化——

      1.减少序列化:
      减少 Java 中的序列化操作可以很好的提升系统性能。序列化大部分是在 RPC 阶段发生,因此应该尽量减少 RPC 调用,一种可行的方案是将多个关联性较强的应用进行 “合并部署”,从而减少不同应用之间的 RPC 调用(微服务设计规范)

      2.直接输出流数据:
      只要涉及字符串的 I/O 操作,无论是磁盘 I/O 还是网络 I/O,都比较耗费 CPU 资源,因为字符需要转换成字节,而这个转换又必须查表编码。所以对于常用数据,比如静态字符串,推荐提前编码成字节并缓存,具体到代码层面就是通过 OutputStream() 类函数从而减少数据的编码转换;另外,热点方法 toString() 不要直接调用 ReflectionToString 实现,推荐直接硬编码,并且只打印 DO 的基础要素和核心要素

      3.裁剪日志异常堆栈:
      无论是外部系统异常还是应用本身异常,都会有堆栈打出,超大流量下,频繁的输出完整堆栈,只会加剧系统当前负载。可以通过日志配置文件控制异常堆栈输出的深度

      4.去组件框架:
      极致优化要求下,可以去掉一些组件框架,比如去掉传统的 MVC 框架,直接使用 Servlet 处理请求。这样可以绕过一大堆复杂且用处不大的处理逻辑,节省毫秒级的时间,当然,需要合理评估你对框架的依赖程度

      总结一下
      性能优化需要一个基准值,所以系统还需要做好应用基线,比如性能基线(何时性能突然下降)、成本基线(去年大促用了多少机器)、链路基线(核心流程发生了哪些变化),通过基线持续关注系统性能,促使系统在代码层面持续提升编码质量、业务层面及时下掉不合理调用、架构层面不断优化改进。

      一致性

      秒杀系统中,库存是个关键数据,卖不出去是个问题,超卖更是个问题。秒杀场景下的一致性问题,主要就是库存扣减的准确性问题。

      减库存的方式
      电商场景下的购买过程一般分为两步:下单和付款。“提交订单”即为下单,“支付订单”即为付款。基于此设定,减库存一般有以下几个方式:

      1.下单减库存。买家下单后,扣减商品库存。下单减库存是最简单的减库存方式,也是控制最为精确的一种

      2.付款减库存。买家下单后,并不立即扣减库存,而是等到付款后才真正扣减库存。但因为付款时才减库存,如果并发比较高,可能出现买家下单后付不了款的情况,因为商品已经被其他人买走了

      3.预扣库存。这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 15 分钟),超过这段时间,库存自动释放,释放后其他买家可以购买
      能够看到,减库存方式是基于购物过程的多阶段进行划分的,但无论是在下单阶段还是付款阶段,都会存在一些问题,下面进行具体分析。

      减库存的问题

      下单减库存
      优势:用户体验最好。下单减库存是最简单的减库存方式,也是控制最精确的一种。下单时可以直接通过数据库事务机制控制商品库存,所以一定不会出现已下单却付不了款的情况。
      劣势:可能卖不出去。正常情况下,买家下单后付款概率很高,所以不会有太大问题。但有一种场景例外,就是当卖家参加某个促销活动时,竞争对手通过恶意下单的方式将该商品全部下单,导致库存清零,那么这就不能正常售卖了——要知道,恶意下单的人是不会真正付款的,这正是 “下单减库存” 的不足之处。

      付款减库存
      优势:一定实际售卖。“下单减库存” 可能导致恶意下单,从而影响卖家的商品销售, “付款减库存” 由于需要付出真金白银,可以有效避免。
      劣势:用户体验较差。用户下单后,不一定会实际付款,假设有 100 件商品,就可能出现 200 人下单成功的情况,因为下单时不会减库存,所以也就可能出现下单成功数远远超过真正库存数的情况,这尤其会发生在大促的热门商品上。如此一来就会导致很多买家下单成功后却付不了款,购物体验自然是比较差的。

      预扣库存
      优势:缓解了以上两种方式的问题。预扣库存实际就是“下单减库存”和 “付款减库存”两种方式的结合,将两次操作进行了前后关联,下单时预扣库存,付款时释放库存。
      劣势:并没有彻底解决以上问题。比如针对恶意下单的场景,虽然可以把有效付款时间设置为 10 分钟,但恶意买家完全可以在 10 分钟之后再次下单。

      小结:
      减库存的问题主要体现在用户体验和商业诉求两方面,其本质原因在于购物过程存在两步甚至多步操作,在不同阶段减库存,容易存在被恶意利用的漏洞。

      实际如何减库存

      业界最为常见的是预扣库存。无论是外卖点餐还是电商购物,下单后一般都有个 “有效付款时间”,超过该时间订单自动释放,这就是典型的预扣库存方案。但如上所述,预扣库存还需要解决恶意下单的问题,保证商品卖的出去;另一方面,如何避免超卖,也是一个痛点。

      卖的出去:恶意下单的解决方案主要还是结合安全和反作弊措施来制止。比如,识别频繁下单不付款的买家并进行打标,这样可以在打标买家下单时不减库存;再比如为大促商品设置单人最大购买件数,一人最多只能买 N 件商品;又或者对重复下单不付款的行为进行次数限制阻断等。

      避免超卖:库存超卖的情况实际分为两种。对于普通商品,秒杀只是一种大促手段,即使库存超卖,商家也可以通过补货来解决;而对于一些商品,秒杀作为一种营销手段,完全不允许库存为负,也就是在数据一致性上,需要保证大并发请求时数据库中的库存字段值不能为负,一般有多种方案:一是在通过事务来判断,即保证减后库存不能为负,否则就回滚;二是直接设置数据库字段类型为无符号整数,这样一旦库存为负就会在执行 SQL 时报错;三是使用 CASE WHEN 判断语句:

      UPDATE item SET inventory
      CASE WHEN inventory
      xxx THEN inventory
      xxx ELSE inventory

      业务手段保证商品卖的出去,技术手段保证商品不会超卖,库存问题从来就不是简单的技术难题,解决问题的视角是多种多样的。

      一致性性能的优化
      库存是个关键数据,更是个热点数据。对系统来说,热点的实际影响就是 “高读” 和 “高写”,也是秒杀场景下最为核心的一个技术难题

      高并发读
      秒杀场景解决高并发读问题,关键词是“分层校验”。即在读链路时,只进行不影响性能的检查操作,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求等,而不做一致性校验等容易引发瓶颈的检查操作;直到写链路时,才对库存做一致性检查,在数据层保证最终准确性。

      因此,在分层校验设定下,系统可以采用分布式缓存甚至 LocalCache 来抵抗高并发读。即允许读场景下一定的脏数据,这样只会导致少量原本无库存的下单请求被误认为是有库存的,等到真正写数据时再保证最终一致性,由此做到高可用和一致性之间的平衡。

      实际上,分层校验的核心思想是:不同层次尽可能过滤掉无效请求,只在“漏斗” 最末端进行有效处理,从而缩短系统瓶颈的影响路径。

      高并发写
      高并发写的优化方式,一种是更换 DB 选型,一种是优化 DB 性能,以下分别进行讨论。

      1、更换DB选型
      秒杀商品和普通商品的减库存是有差异的,核心区别在数据量级小、交易时间短,因此能否把秒杀减库存直接放到缓存系统中实现呢,也就是直接在一个带有持久化功能的缓存中进行减库存操作,比如 Redis?

      如果减库存逻辑非常单一的话,比如没有复杂的 SKU 库存和总库存这种联动关系的话,个人认为是完全可以的。但如果有比较复杂的减库存逻辑,或者需要使用到事务,那就必须在数据库中完成减库存操作。

      2、优化DB性能
      库存数据落地到数据库实现其实是一行存储(MySQL),因此会有大量线程来竞争 InnoDB 行锁。但并发越高,等待线程就会越多,TPS 下降,RT 上升,吞吐量会受到严重影响——注意,这里假设数据库已基于上文【性能优化】完成数据隔离,以便于讨论聚焦 。

      解决并发锁的问题,有两种办法:

      应用层排队。
      通过缓存加入集群分布式锁,从而控制集群对数据库同一行记录进行操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用过多的数据库连接

      数据层排队。
      应用层排队是有损性能的,数据层排队是最为理想的。业界中,阿里的数据库团队开发了针对 InnoDB 层上的补丁程序(patch),可以基于 DB 层对单行记录做并发排队,从而实现秒杀场景下的定制优化——注意,排队和锁竞争是有区别的,如果熟悉 MySQL 的话,就会知道 InnoDB 内部的死锁检测,以及 MySQL Server 和 InnoDB 的切换都是比较消耗性能的。另外阿里的数据库团队还做了很多其他方面的优化,如 COMMIT_ON_SUCCESS 和 ROLLBACK_ON_FAIL 的补丁程序,通过在 SQL 里加入提示(hint),实现事务不需要等待实时提交,而是在数据执行完最后一条 SQL 后,直接根据 TARGET_AFFECT_ROW 的结果进行提交或回滚,减少网络等待的时间(毫秒级)。目前阿里已将包含这些补丁程序的 MySQL 开源:AliSQL

      小结:
      高读和高写的两种处理方式大相径庭。读请求的优化空间要大一些,而写请求的瓶颈一般都在存储层,优化思路的本质还是基于 CAP 理论做平衡。
      总结一下:
      当然,减库存还有很多细节问题,例如预扣的库存超时后如何进行回补,再比如第三方支付如何保证减库存和付款时的状态一致性,这些也是很大的挑战。

      高可用

      盯过秒杀流量监控的话,会发现它不是一条蜿蜒而起的曲线,而是一条挺拔的直线,这是因为秒杀请求高度集中于某一特定的时间点。这样一来就会造成一个特别高的零点峰值,而对资源的消耗也几乎是瞬时的。所以秒杀系统的可用性保护是不可或缺的。

      流量削峰
      对于秒杀的目标场景,最终能够抢到商品的人数是固定的,无论 100 人和 10000 人参加结果都是一样的,即有效请求额度是有限的。并发度越高,无效请求也就越多。但秒杀作为一种商业营销手段,活动开始之前是希望有更多的人来刷页面,只是真正开始后,秒杀请求不是越多越好。因此系统可以设计一些规则,人为的延缓秒杀请求,甚至可以过滤掉一些无效请求。

      答题
      早期秒杀只是简单的点击秒杀按钮,后来才增加了答题。为什么要增加答题呢?主要是通过提升购买的复杂度,达到两个目的:
      1.防止作弊。早期秒杀器比较猖獗,存在恶意买家或竞争对手使用秒杀器扫货的情况,商家没有达到营销的目的,所以增加答题来进行限制
      2.延缓请求。零点流量的起效时间是毫秒级的,答题可以人为拉长峰值下单的时长,由之前的 <1s 延长到 <10s。这个时间对于服务端非常重要,会大大减轻高峰期并发压力;另外,由于请求具有先后顺序,答题后置的请求到来时可能已经没有库存了,因此根本无法下单,此阶段落到数据层真正的写也就非常有限了

      排队
      最为常见的削峰方案是使用消息队列,通过把同步的直接调用转换成异步的间接推送缓冲瞬时流量。除了消息队列,类似的排队方案还有很多,例如:
      1.线程池加锁等待
      2.本地内存蓄洪等待
      3.本地文件序列化写,再顺序读
      排队方式的弊端也是显而易见的,主要有两点:
      1.请求积压。流量高峰如果长时间持续,达到了队列的水位上限,队列同样会被压垮,这样虽然保护了下游系统,但是和请求直接丢弃也没多大区别
      2.用户体验。异步推送的实时性和有序性自然是比不上同步调用的,由此可能出现请求先发后至的情况,影响部分敏感用户的购物体验
      排队本质是在业务层将一步操作转变成两步操作,从而起到缓冲的作用,但鉴于此种方式的弊端,最终还是要基于业务量级和秒杀场景做出妥协和平衡。

      过滤
      过滤的核心结构在于分层,通过在不同层次过滤掉无效请求,达到数据读写的精准触发。常见的过滤主要有以下几层:
      1.读限流:对读请求做限流保护,将超出系统承载能力的请求过滤掉
      2.读缓存:对读请求做数据缓存,将重复的请求过滤掉
      3.写限流:对写请求做限流保护,将超出系统承载能力的请求过滤掉
      4.写校验:对写请求做一致性校验,只保留最终的有效数据
      过滤的核心目的是通过减少无效请求的数据 IO 保障有效请求的 IO 性能。

      小结:
      系统可以通过入口层的答题、业务层的排队、数据层的过滤达到流量削峰的目的,本质是在寻求商业诉求与架构性能之间的平衡。另外,新的削峰手段也层出不穷,以业务切入居多,比如零点大促时同步发放优惠券或发起抽奖活动,将一部分流量分散到其他系统,这样也能起到削峰的作用。

      Plan B
      当一个系统面临持续的高峰流量时,其实是很难单靠自身调整来恢复状态的,日常运维没有人能够预估所有情况,意外总是无法避免。尤其在秒杀这一场景下,为了保证系统的高可用,必须设计一个 Plan B 方案来进行兜底。

      高可用建设,其实是一个系统工程,贯穿在系统建设的整个生命周期。
      图片2.png

      具体来说,系统的高可用建设涉及架构阶段、编码阶段、测试阶段、发布阶段、运行阶段,以及故障发生时,逐一进行分析:

      架构阶段:考虑系统的可扩展性和容错性,避免出现单点问题。例如多地单元化部署,即使某个 IDC 甚至地市出现故障,仍不会影响系统运转

      编码阶段:保证代码的健壮性,例如 RPC 调用时,设置合理的超时退出机制,防止被其他系统拖垮,同时也要对无法预料的返回错误进行默认的处理

      测试阶段:保证 CI 的覆盖度以及 Sonar 的容错率,对基础质量进行二次校验,并定期产出整体质量的趋势报告

      发布阶段:系统部署最容易暴露错误,因此要有前置的 checklist 模版、中置的上下游周知机制以及后置的回滚机制

      运行阶段:系统多数时间处于运行态,最重要的是运行时的实时监控,及时发现问题、准确报警并能提供详细数据,以便排查问题

      故障发生:首要目标是及时止损,防止影响面扩大,然后定位原因、解决问题,最后恢复服务

      对于日常运维而言,高可用更多是针对运行阶段而言的,此阶段需要额外进行加强建设,主要有以下几种手段:

      预防:建立常态压测体系,定期对服务进行单点压测以及全链路压测,摸排水位

      管控:做好线上运行的降级、限流和熔断保护。需要注意的是,无论是限流、降级还是熔断,对业务都是有损的,所以在进行操作前,一定要和上下游业务确认好再进行。就拿限流来说,哪些业务可以限、什么情况下限、限流时间多长、什么情况下进行恢复,都要和业务方反复确认

      监控:建立性能基线,记录性能的变化趋势;建立报警体系,发现问题及时预警

      恢复:遇到故障能够及时止损,并提供快速的数据订正工具,不一定要好,但一定要有

      在系统建设的整个生命周期中,每个环节中都可能犯错,甚至有些环节犯的错,后面是无法弥补的或者成本极高的。所以高可用是一个系统工程,必须放到整个生命周期中进行全面考虑。同时,考虑到服务的增长性,高可用更需要长期规划并进行体系化建设。

      总结一下:
      高可用其实是在说 “稳定性”,稳定性是一个平时不重要,但出了问题就要命的事情,然而它的落地又是一个问题——平时业务发展良好,稳定性建设就会降级给业务让路。解决这个问题必须在组织上有所保障,比如让业务负责人背上稳定性绩效指标,同时在部门中建立稳定性建设小组,小组成员由每条线的核心力量兼任,绩效由稳定性负责人来打分,这样就可以把体系化的建设任务落实到具体的业务系统中了。

      个人总结
      一个秒杀系统的设计,可以根据不同级别的流量,由简单到复杂打造出不同的架构,本质是各方面的取舍和权衡。当然,你可能注意到,本文并没有涉及具体的选型方案,因为这些对于架构来说并不重要,作为架构师,应该时刻提醒自己主线是什么。

      同时也在这里抽象、提炼一下,主要是个人对于秒杀设计的提纲式整理,方便各位同学进行参考!

      图片3.png

      ]]>
      展望下一代医疗集成平台-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800  【导读】 医疗信息集成在我国医院信息化的发展中,已经落地实施了将近十年。这十年间,信息集成平台的建设,从一、两家的最佳实践案例到全国医院的大规模实施,再到国家卫健委的互联互通标准化测评,已经经过了行业内的充分论证与实践,取得了蓬勃的发展。那么,究竟集成平台需要具备哪些特性,才能更好地满足:1) 区域共享;2)数据应用;3)资源贯通;4)业务协同?

      集成平台:在线增量备份与任意时间点恢复

      集成平台承载了医院的核心业务,因而从保障业务连续性角度必须做到有“备”而无患,常见的解决方案有双机冷备和双机热备。
      总体说来,双机即两台服务器,一台主机(Master)运行(Active),一台备用(Slave) 机待命(Stand-by)。一旦主机宕机,由备用机临时接管工作。冷备需手动切换到备用机,热备可实现自动切换。 双机热备将数据在主机和备机中自动同步,主机宕机自动切换至副机,保证系统高可用性和业务连续性。

      冷备份, 以及逻辑备份都是某一个时间点的备份, 没有增量的概念。如果数据库在运行过程中发生故障, 使用逻辑备份只能将数据库还原到备份时刻, 无法恢复到故障发生前的那个时刻。并且无法解决在使用过程中由于误操作修改或删除了重要数据, 需要还原到误操作前的那个时刻等类似需求。使用冷备份加上有效的归档文件可以实现任意时间点的恢复。但是冷备份需要停库操作, 无法保障医院的核心业务的连续性。

      所以新一代集成平台需要支持在线的增量备份, 同时又支持基于时间点的恢复。

      集成引擎、ESB、ETL:“三合一”

      无论医院级别大小,各医疗机构系统情况不一,集成需求多样化,对集成+ESB+ETL的混合型业务需求日益增多。

      将强调消息有序性和保证传输的集成引擎,着重服务的ESB、和数据抽取转换上报(ETL)功能三合一至一个中间件,使得医院不必购买多套软件产品,集成平台的使用和维护也变得更加便捷,并且提高了集成平台的综合性能和稳定性。

      内嵌国内CDA标准:涵盖医院和区域

      将国内 CDA 标准(《电子病历共享文档规范》及《卫生信息共享文档》)内嵌至下一代集成平台,提供用户对CDA及数据集进行自定义的功能,满足实际互联互通项目实施时的微调需求。界面包含清晰的互联互通服务的统计信息,进一步助力互联互通测评工作,满足各级各类医院集成平台信息传输与交换层面的规范、统一需求,助力实现医院信息平台跨机构、跨区域交换与共享,有力促进业务协同;

      CDAAAA.jpg

      对大数据系统的内嵌支持:Hadoop, Kafka

      随着医院信息化建设不断发展,医疗数据规模日益增长,医疗服务全面进入“大数据时代”。下一代集成平台需要以临床数据为核心,采用智能化的双向数据采集工具,实时聚集各类医疗业务数据,进行标准化处理,搭建主题数据仓库,形成高质量的大数据资产,结合人工智能算法和数据分析模型,满足医院信息共享与应用的需求。

      数据中心.jpg

      由此可见,集成平台的对大数据系统的支持日显重要。集成平台可内嵌支持对HDFS分布式文件系统的读写访问操作;也可以通过Phoenix进行桥接对Hadoop 系统操作;提供连接Kafka系统的终端实现对Kafka 分布式流平台支持,实现对大数据的支持。

      展望下一代医疗集成平台

      预见到,未来医院和区域信息平台的发展呈现如下趋势:
      分布式集群部署,保障服务稳定,可动态扩展
      完善的标准化原子服务库,满足医院业务需求
      图形化服务配置,快速实现服务封装发布
      支持海量数据实时采集,采用分布式集群模式
      全面整合医院数据资源,形成有效的数据资产,深度挖掘数据价值
      遵循国卫标准,符合HL7、CDA及IHE等标准
      提供丰富的适配器组件,满足各种接入需求

      ]]>
      Istio 网关之南北向流量管理(内含服务网格专家亲自解答)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 Istio 网关

      网络社区中有一个术语 Ingress,是指入口请求到集群内服务的流量管理。Ingress 指的是源自本地网络之外的流量,指向本地集群网络中的端点。此流量首先路由到公开的入口点,以便通过执行一些本地网络的规则和策略来确认哪些流量被允许进入。如果流量未通过这些入口点,则无法与集群内的任何服务连接。如果入口点允许流量进入,则将其代理到本地网络中的合适节点。Istio 对入口流量的管理是由 Istio 网关进行的。

      Istio 网关的工作原理

      传统上,Kubernetes 使用 Ingress 控制器来处理从外部进入集群的流量。使用 Istio 时,情况不再如此。Istio 网关用新的 Gateway 资源和 VirtualServices 资源来控制入口流量,它们协同工作以将流量路由到网格中。在网格内部不需要 Gateways,因为服务可以通过集群本地服务名称相互访问。

      那么 Istio 网关是怎样工作的?请求如何到达它想要的应用程序?基本步骤如下:

      1.客户端在特定端口上发出请求;
      2.负载均衡器在这个端口上进行侦听,并将请求转发到集群中(在相同或新的端口);
      3.在集群内部,请求被路由到 Istio IngressGateway 服务所侦听的负载均衡器转发过来的端口上;
      4.Istio IngressGateway 服务将请求(在相同或新的端口)转发到对应的 pod 上;
      5.在 IngressGateway pod 上会配置 Gateway 资源和 VirtualService 资源定义。Gateway 会配置端口、协议以及相关安全证书。VirtualService 的路由配置信息用于找到正确的服务;
      6.Istio IngressGateway pod 会根据路由配置信息将请求路由到对应的应用服务上;
      7.应用服务将请求路由到对应的应用 pod 上。

      image.png

      (tio 网关的工作原理)

      Istio 网关的负载均衡作用

      典型的服务网格具有一个或多个负载均衡器,也称为网关(Gateway),它们从外部网络终止 TLS 并允许流量进入网格。然后,流量通过边车网关(Sidecar gateway)流经内部服务。应用程序使用外部服务的场景也很常见,可以直接调用外部服务,或者在某些部署中强制通过专用出口网关(Egress Gateway)离开网格的所有流量。

      Istio 具有入口网关的概念,它扮演网络入口点的角色,负责保护和控制来自集群外部的流量对集群的访问。

      image.png
      (网关在网格中的使用情况)

      此外,Istio 的网关还扮演负载均衡和虚拟主机路由的角色。如图所示,可以看到默认情况下 Istio 使用 Envoy 代理作为入口代理。Envoy 是一个功能强大的服务到服务代理,但它也有负载均衡和路由的功能,可代理的流量包括从服务网格外部到其内部运行的服务,或者从集群内部服务到外部服务。在前面章节中介绍的 Envoy 的所有功能也可以在入口网关中使用。

      image.png
      (Istio 的入口网关服务)

      对于入口流量管理,你可能会问:为什么不直接使用 Kubernetes Ingress API?

      • 第一个原因,Kubernetes Ingress 是一个面向 HTTP 工作负载的非常简单的规范。有 Kubernetes Ingress 的实现(如 Nginx、Heptio Contour 等),但每个都适用于 HTTP 流量。实际上,Ingress 规范只将端口 80 和端口 443 视为入口点。这严重限制了集群运维人员可以允许进入服务网格的流量类型。例如,如果你有 Kafka 工作负载,则可能希望向这些消息代理公开直接 TCP 连接;
      • 第二个原因,Kubernetes Ingress API 无法表达 Istio 的路由需求。Ingress 没有通用的方法来指定复杂的流量路由规则,如流量拆分或流量镜像等。这个领域缺乏规范会导致每个供应商重新设想如何更好地为每种类型的 Ingress 实现(如 HAProxy、Nginx 等)做好配置管理。Ingress 试图在不同的 HTTP 代理之间取一个公共的交集,因此只能支持最基本的 HTTP 路由;
      • 最后一个原因,由于事前没有明确规定,大多数供应商的选择是通过部署上的定制注释来做配置。供应商之间的注释各不相同,并且不可移植,如果 Istio 继续延续这种趋势,那么就会有更多的注释来解释 Envoy 作为边缘网关的所有功能。

      Istio 网关通过将 L4-L6 配置与 L7 配置分离克服了 Ingress 的这些缺点。Istio 网关只用于配置 L4-L6 功能(例如,对外公开的端口、TLS 配置),所有主流的 L7 代理均以统一的方式实现了这些功能。然后,通过在 Gateway 上绑定 VirtualService 的方式,可以使用标准的 Istio 规则来控制进入 Gateway 的 HTTP 和 TCP 流量。负载均衡器可以手动配置或通过服务自动配置其类型,例如 type: LoadBalancer。在这种情况下,由于并非所有云都支持自动配置,假设手动配置负载均衡器以将流量转发到 IngressGateway Service 正在侦听的端口。例如如下的负载均衡器正在监听以下端口:

      • HTTP:端口 80,将流量转发到端口 30080;
      • HTTPS:端口 443,将流量转发到端口 30443;
      • MySQL:端口 3306,将流量转发到端口 30306 确保负载均衡器配置转发到所有工作节点。这将确保即使某些节点关闭也会转发流量。

      入口网关服务

      IngressGateway 服务(入口网关服务)必须监听上节介绍的所有端口,以便能够将流量转发到 IngressGateway pod 上。Kubernetes 服务不是“真正的”服务,该请求将由 Kubernetes 提供的 kube-proxy 转发到具有运行对应 pod 的节点上。在节点上,IP table 配置将请求转发到适当的 pod:

      ports:

      • name: http2
        nodePort: 30000
        port: 80
        protocol: TCP
      • name: https
        nodePort: 30443
        port: 443
        protocol: TCP
      • name: mysql
        nodePort: 30306
        port: 3306
        protocol: TCP

      入口网关部署

      IngressGateway 部署是一个基于 Envoy 代理的封装,它的配置方式与服务网格中使用的 Sidecar 配置相同(实际上是同样的容器镜像)。当我们创建或更改一个 Gateway 或 VirtualService 时,Istio Pilot 控制器会检测到这些变更,并将这些变更信息转换为 Envoy 配置,然后将 Envoy 配置信息发送给相关 Envoy 代理,包括内部的 Envoy 和 IngressGateway 中的 Envoy。

      注意:这里不要混淆 IngressGateway 与 Gateway,Gateway 资源是用于配置 IngressGateway 的一种 Kubernetes 的自定义资源。

      由于不必在 Kubernetes pod 或部署中声明容器端口,因此我们不必在 IngressGateway Deployment 中声明端口。但是,如果查看部署内部,可以看到声明的许多端口。另外,在 IngressGateway 部署中需要关注 SSL 证书,为了能够访问 Gateway 资源内的证书,请确保已正确加载这些证书。

      网关资源

      网关资源用来配置 Envoy 的端口,前面的示例中已经使用该服务公开了三个端口,因此需要在 Envoy 中处理这些端口。此外,可以通过声明一个或多个 Gateways 来支持多端口能力。下面的示例中使用单个 Gateway,但可以分为两个或三个分别定义:

      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
      name: default-gateway
      namespace: istio-system
      spec:
      selector:

      istio: ingressgateway

      servers:

      • hosts:

        • '*'
          port:

        name: http
        number: 80
        protocol: HTTP

      • hosts:

        • '*'
          port:

        name: https
        number: 443
        protocol: HTTPS
        tls:
        mode: SIMPLE
        privateKey: /etc/istio/ingressgateway-certs/tls.key
        serverCertificate: /etc/istio/ingressgateway-certs/tls.crt

      • hosts: # For TCP routing this fields seems to be ignored, but it is matched

        • '' # with the VirtualService, I use since it will match anything.
          port:

        name: mysql
        number: 3306
        protocol: TCP

      网关虚拟服务

      VirtualService 资源与 Gateway 资源相互配合支持 Envoy 的配置。下面是一个支持 HTTP 服务的网关虚拟服务的基本配置:

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
      name: counter
      spec:
      gateways:

      • default-gateway.istio-system.svc.cluster.local
        hosts:
      • counter.lab.example.com
        http:
      • match:

        • uri:
          prefix: /

        route:

        • destination:

          host: counter
          port:
            number: 80
          

      现在,当我们添加一个 Gateway 和一个 VirtualService 时,路由已在 Envoy 配置中创建。要查看此内容,你可以使用如下命令:

      kubectl port-forward istio-ingressgateway-xxxx-yyyy-n istio-system 15000

      调试入口网关

      调试网络问题有时很困难,所以这里总结一些有用的命令用于调试。端口转发到第一个 istio-ingressgateway pod:

      kubectl -n istio-system port-forward $(kubectl -n istio-system get pods
      -listio=ingressgateway -o=jsonpath="{.items[0].metadata.name}") 15000

      然后,可以从端口转发的入口网关 pod 中获得 http 路由:

      Curl --silent http://localhost:15000/config_dump |jq .configs[3].dynamic_route_configs[].route_config.virtual_hosts[]

      image.png
      (端口转发的入口网关 pod)

      查看上述端口转发的入口网关 pod 的日志信息:

      kubectl -n istio-system logs $(kubectl -n istio-system get pods
      -listio=ingressgateway -o=jsonpath="{.items[0].metadata.name}") --tail=300

      查看 Pilot pod 的日志信息:

      kubectl -n istio-system logs $(kubectl -n istio-system get pods
      -listio=pilot -o=jsonpath="{.items[0].metadata.name}") discovery --tail=300

      当启动端口转发到入口网关 istio-ingressgateway 之后,可以执行更多操作以获取更多信息,例如:

      关于 Istio 的一些 Q&A

      Q1:Istio 在实际生产环境实践中都会遇到哪些瓶颈,常见的优化手段又都有哪些?
      A1:阿里云之所以推出服务网格 ASM 这个产品,就是把过去一两年支持客户使用 Istio 的过程中遇到的太多问题,总结成了经验并落地到产品中。所以多关注下这个产品的能力就会看到具体解决了哪些问题。在此就不一一赘述了。

      Q2:Istio 会导致性能消耗增加吗?
      A2:这个问题需要一个上下文,这就像问 Java 虚拟机会导致性能消耗吗。任何解耦总会带来一定的通信消耗。建议使用 Istio 前先判断下自己的应用是否适合解耦、服务化以及容器化,然后再看是否适合采用 Istio 的哪些功能。

      Q3:Service Mesh 是很好的工具,但是痛点也很明显,引入 Istio 会让运维更加复杂,阿里云服务网格产品 ASM 这方面有做什么改进?
      A3:阿里云服务网格 ASM 提供了一个全托管式的服务网格平台,兼容于社区 Istio 开源服务网格,用于简化服务的治理,并提供了简单易用的控制台,托管模式下让用户解脱控制面的复杂管理,极大地减轻开发与运维的工作负担。具体可以参考这个入门教程了解一下:https://help.aliyun.com/document_detail/149552.html

      Q4:最近在研究 Istio,有真正落地使用的例子吗?
      A4:过去一两年我们已经支持了大量客户使用 Istio,譬如有客户使用 Istio 流量管理来做应用的灰度发布,有客户使用它的统一的声明式的方式来管理入口网关,包括入口网关的 tls 透传功能、tls 终止以及动态加载证书的能力等等。

      Q5:目前阿里服务网格产品 ASM 针对 Istio 采用什么样的监控?
      A5:阿里云服务网格 ASM 的可观测性能力从三个维度提供了不同的能力,包括:

      Q6:阿里的 ASM 的 Proxy 会采用 MOSN 吗?期待 MOSN 成为 Istio 可选数据平面之一。
      A6:阿里云服务网格 ASM 从一开始的设计就是兼容于社区 Istio 开源服务网格,只要符合与 Istio 控制面规约要求的数据面代理,从理论上都是可以支持的。当然一个新代理的适配是需要一定量的开发工作,这儿我们也想了解下客户对于这方面的诉求。

      Q7:Istio 也有类似 Linkerd 的 mutls 功能吗?
      A7:Istio 默认已经提供了双向 tls 认证的功能,而且支持渐进式,包括 permissive 和 strict 两种方式。

      本文转自<阿里巴巴云原生技术圈>——阿里巴巴云原生小助手

      ]]>
      做深基础,助力新基建,阿里云多款存储产品正式发布-阿里云开发者社区 Fri, 20 Jun 2025 02:20:33 +0800 6月23日,阿里云存储新品发布会再次来袭,在数字新基建时代,阿里云存储产品将沿着“做深基础”的再生长方向,全面助力客户的数字化转型之路。
      lADPD2eDMulZQEnNAe3NAww_780_493.jpg

      1、日志服务SLS,进一步打造AI-DevOps的平台与生态

      阿里云推出的日志服务SLS已经成为行业领先的日志大数据平台,提供一站式数据采集、清洗、存储、分析、可视化和告警的功能。此次,日志服务SLS的时序监控与诊断平台正式发布,除了提供海量日志处理能力,还支持了时序监控数据存储与分析,提供更加便捷、高性能、低成本的时序监控诊断解决方案。
      日志.png

      同时,日志服务SLS发布了SIEM(Security information and event management ) 和 SOC(Security Operations Center)开放对接能力,企业客户可以利用日志服务SLS的日志统一采集和清洗能力,将云上系统的合规、安全等相关日志,统一导入到现有的安全运营中心(SOC)。本次,日志服务SLS的日志审计功能,也新增更多产品的覆盖,并提供修改删除保护策略,支持多账号、多地域、多产品的日志统一采集,并且开放与第三方SOC对接,更好地服务企业构建日志统一审计中心。此外,日志服务SLS还发布了K8S事件中心,满足客户在微服务、容器的技术趋势下,一站式对Kurbernets事件的实时采集、分析、告警和可视化需求。

      日志服务SLS已支持20多款云产品日志支持一键接入,并提供了分析模板、内置报表。本次发布,日志服务新增了IoT、Redis、MaxCompute日志源一键接入与分析,满足用户对IoT设备状态、Redis审计或慢请求和错误请求、MaxCompute操作日志的分析、异常告警、统计报表等需要。

      2、对象存储OSS,支持国密算法

      在安全这方面,合规监管以及安全性的要求将越来越严格,为了满足安全合规方面的诉求,对象存储OSS在原有的服务端加密功能的基础上,全面支持了国密算法。

      阿里云对象存储在加密方面的功能十分完善,不仅能支持用户可见、可管理的BYOK加密,同时能支持全托管加密方式以及基于KMS平台管理的加密方式,基于这三种不同的加密方式,在原有的仅支持AES256算法的基础上,又进一步的开发了对国密算法的支持,丰富了阿里云对象存储对加密算法的支持度,更好的满足了企业客户在安全合规上多维度的需求。

      除新的加密算法之外,对象存储OSS还针对数据湖场景中,临时目录中的文件重命名功能进行了优化,当对象存储接收到数据重命名的指令时,直接快速更新对象对应的元数据,而对象本身不做任何的操作,从而快速的实现文件的重命名动作,并达到亚秒级别的数据返回效率,实现“瞬时拷贝”。
      瞬间拷贝.jpg

      阿里云对象存储OSS还推出了Bucket 清单功能,只需要在控制台做简单的配置就可以定期地获取到针对Bucket粒度所有Object的列表,同时可以针对某个目录级别(前缀)来做分目录的清单导出,数据导出后可以根据业务诉求来做针对性的分析与处理。除此之外,Bucket 清单功能还能够与阿里云日志服务SLS结合使用实现业务数据分析的智能化与可视化。

      3、表格存储Tablestore,支持投递OSS,全面进入数据湖生态

      表格存储Tablestore是基于共享存储的高性能、低成本、易扩展、全托管的结构化大数据存储平台,支撑互联网、物联网等数据的高效计算与分析。目前,表格存储Tablestore作为承载钉钉核心消息系统的存储产品,经受住了移动办公浪潮的艰巨考验。

      此次,表格存储Tablestore发布了数据投递对象存储OSS功能,打造了一个可实现数据分层的数据湖方案。

      表格存储.png

      数据投递对象存储OSS兼容开源生态标准,支持按照Parquet等格式存储,支持Hive命名约定,无缝兼容Spark等计算生态。同时数据投递自动实时同步,可一键完成配置,无需维护复杂的datapipeline。基于这些特点,一方面针对近期的数据,表格存储Tablestore可以保证数据的实时可见性,支持数据点查与多维度检索,以及近期数据的分析查询;另一方面针对离线数据,投递对象存储OSS数据湖,实现分析的高效与成本的可控。

      在这样的数据湖方案中,表格存储Tablestore可以更好的发挥应用中数据的价值,广泛在电商、短视频、新闻等需要支持海量结构化数据存储、实时或离线分析的应用场景中,帮助客户更快、更省地对数据进行应用与分析。

      4、混合云存储网关,让上云更简单

      云存储网关以阿里云对象存储OSS为后端存储,为企业应用提供行业标准的文件存储(NFS/SMB)和iSCSI块存储等服务。云存储网关可以部署在客户数据中心,也可以部署在阿里云上,帮助客户简化存储管理,实现企业应用和阿里云存储服务的无缝对接。

      首先,云存储网关基于对象存储OSS,提供海量存储空间、智能对接低频/归档存储,冷热数据分层以及灵活的付费方式进一步降低TCO;其次,企业应用无需改造平滑上云,端到端数据加密保证客户数据安全;再次,在企业数据平滑上云、文件应用对接云存储、备份/归档、视频监控、机器学习等场景,云存储网关具有广泛的应用;最后,云存储网关集成了智能缓存算法,自动识别冷热数据,将热数据保留本地在本地缓存,保证数据访问体验,无感知的将海量云存储接入本地数据中心,拓展存储空间。同时在云端保留全量数据(冷+热)保证数据的一致性。

      在实际工作中,企业的分支机构之间数据的及时共享以及协作一直以来都是很大的挑战,云存储网关在配合对象存储OSS,同时借助传输加速等方案,帮助客户解决了多站点数据共享的问题,实现了全球多站点文件共享(Multi-site Sync and Sharing)。
      网关.png

      首先,实现跨区域部署,全局统一命名空间,多站点文件共享与协同;其次,文件属性即时同步,文件数据按需拉取;第三,支持多个文件网关间数据亚秒级同步。目前,该方案已经满足某自动驾驶公司将新增数十万张图片由总部分发到不同地域做AI计算的实际需求。

      5、ESSD云盘新规格发布,降低全闪门槛

      全闪存储已成趋势,但使用门槛仍然很高,阿里云此次全新发布的入门级全闪云盘规格——ESSD PL0。
      ESSD云盘.png

      ESSD PL0作为ESSD系列入门级规格,采用与ESSD系列相同的技术架构,同样拥有亚毫秒级别的延时,单卷最大支持10000的IOPS。相比于上一代入门级云盘,每月仅需多花0.15元/GB,即可使用全闪云盘,大幅降低了全闪存储的使用门槛,并且可以与阿里云第六代增强型实例完美搭配。

      ESSD PL0适用于多种业务场景。例如:小型数据库场景、DevOps场景、系统盘场景。线上实测“搭载ESSD PL0的六代增强型实例”启动速度提升了60%以上;此外针对DevOps场景,基于ESSD系列云盘的“本地快照”特性,能够在数十秒内快速完成快照创建以及云盘创建操作,将业务等待时间缩短至分钟级别;针对小型数据库场景,基于ESSD PL0云盘搭建的数据库实测TPS性能相比于高效云盘提升2倍以上。

      此次全新发布的ESSD PL0 采用NVMe存储介质,最大程度上释放阿里云的技术红利。这也是业内唯一一款基于“RDMA网络架构+NVMe存储介质”的入门级云盘。ESSD PL0的发布使得ESSD系列的使用门槛降低50%,且后续支持无损变配到ESSD系列更高规格,普惠阿里云上百万客户。目前ESSD PL0已经在北京、杭州区域公测上线。

      ]]>
      【其他】6月30日DDoS防护能力调整通知 Fri, 20 Jun 2025 02:20:34 +0800 【阿里云】【DDoS高防】【防护能力调整通知

      调整时间:北京时间2020年6月30日 23:00 - 24:00

      调整内容:DDoS高防(旧版)防护能力调整后,最大支持300G防护能力,不再提供保底和弹性大于300G的防护服务
      调整影响:调整期间,业务无影响。如果您正在使用DDoS高防(旧版)并且需要大于300G防护能力,我们提供平滑切换到DDoS高防(新BGP)的免费迁移服务,DDoS高防(新BGP)可以满足您大于300G的防护能力需求。给您带来的不便敬请谅解,有任何问题,可随时通过工单或服务电话95187联系反馈。


      ]]>
      在 Eclipse 中使用 Cloud Toolkit 快速部署应用至 ECS 集群_使用工具部署应用_应用部署_ECS集群用户指南_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 在 Eclipse 中使用 Cloud Toolkit 快速部署应用至 ECS 集群

      更新时间:2020-06-08 10:54:43

      本页目录

      您在本地完成应用的开发、调试和测试后,可以使用在本地 IDE (现已支持 Eclipse、IntelliJ IDEA)中安装的 Cloud Toolkit 插件连接到云端部署环境并将应用快速部署到 EDAS 的 ECS 集群中。本文档将向您介绍如何在 Eclipse 中安装 Cloud Toolkit,并使用 Cloud Toolkit 快速部署一个应用到 EDAS。

      前提条件

      步骤一:安装 Cloud Toolkit

      1. 启动 Eclipse。
      2. 在菜单栏中选择Help > Install New Software
      3. Available Software 对话框的 Work with 文本框中输入 Cloud Toolkit for Eclipse 的 URL http://toolkit.aliyun.com/eclipse/
      4. 在下面的列表区域中勾选需要的组件 Alibaba Cloud Toolkit CoreAlibaba Cloud Toolkit Deployment Tools,并在下方 Details 区域中不勾选 Connect all update sites during install to find required software。完成组件选择之后,单击Nextedas-cloudtoolkit-installconfig.png
      5. 按照 Eclipse 安装页面的提示,完成后续安装步骤。

        注意 安装过程中可能会提示没有数字签名,选择 Install anyway 即可。

      6. Cloud Toolkit 插件安装完成后,重启 Eclipse,您可以在工具栏看到 Alibaba Cloud Toolkit 的图标。 edas-cloudtoolkit-toolbaricon.png

      步骤二:配置 Cloud Toolkit 账号

      您需使用 Access Key ID 和 Access Key Secret 来配置 Cloud Toolkit 的账号。

      1. 启动 Eclipse。
      2. 在工具栏单击 Alibaba Cloud Toolkit 图标右侧的下拉按钮,在下拉菜单中单击 Alibaba Cloud Preference
      3. Preference (Filtered) 对话框的左侧导航栏中单击 Accounts
      4. Accounts 界面中设置 Access Key IDAccess Key Secret,然后单击 OK

        注意 如果您使用子账号的Access Key ID和Access Key Secret,请确认该子账号至少拥有部署应用的权限,具体操作方式请参见子账号管理

        edas-cloudtoolkit-config-AK-SK.png
        • 如果您已经注册过阿里云账号,在 Accounts 界面中单击 Manage existing Acount,进入阿里云登录页面。用已有账号登录后,跳转至安全信息管理页面,获取 Access Key IDAccess Key Secret
        • 如果您还没有阿里云账号,在 Accounts 界面中单击 Sign up,进入阿里云账号注册页面,注册账号。注册完成后按照上述方式获取 Access Key IDAccess Key Secret

        说明 如果使用 EDAS 专有云企业版,还需要按以下步骤在 Cloud Toolkit 中配置 Endpoint。其中,Endpoint 请联系 EDAS 技术支持获取。

        1. Preference (Filtered) 对话框的左侧导航栏中选择 Appearance & BehaviorEndpoint
        2. Endpoint 界面中设置 Endpoint,配置完成后,单击 Apply and Close

      步骤三:将应用部署到 EDAS

      目前支持使用 Cloud Toolkit 插件将应用通过 WAR 包或 JAR 包部署到 EDAS。

      1. 在 Eclipse 界面左侧的Package Explorer 中右键单击您的应用工程名,在弹出的下拉菜单中选择Alibaba Cloud > Deploy to EDAS
      2. Deploy to EDAS 的运行配置页面,配置应用部署参数,然后单击 Deploy

        说明 如果您还没有在 EDAS 上创建应用,在对话框右上角单击 Create application on EDAS console…,跳转到 EDAS 控制台创建应用。

        edas-cloudtoolkit-eclipse-config-APP.png

        在配置页面中根据您的实际需求选择应用的 RegionNamespaceApplicationGroup

        • Region:应用所在地域。
        • Namespace:应用所在命名空间。
        • Application:应用名称。
        • Group:应用分组。

        注意 如果在应用列表中获取不到应用,请参见常见问题:应用列表获取不到应用进行操作排查。

        1. 设置构建方式。
          • Maven Build:选择 Maven Build 方式来构建应用时,系统会默认添加一个 Maven 任务来构建部署包。
          • Upload File:选择 Upload File 方式来构建应用时,选择上传您的 WAR 包或者 JAR 包,然后进行部署。
        2. 设置应用的版本描述信息和分批部署信息。
          • Version:部署版本。
          • Description:部署信息描述。
          • Batch:分批数。如果您的应用有多个分组,并且在部署时选择部署全部分组,那么将会自动按照分组粒度来分批,Batch 值不用设置。
          • BatchWaitTime:分批部署等待时间,单位为分钟。

          注意 如果您的插件界面没有分批部署设置模块,请将您的插件升级至最新版本。

      3. 部署开始后,Eclipse 的 Console 区域会打印部署日志。您可以根据日志信息检查部署结果。

      步骤四:终止 Cloud Toolkit 插件运行

      在插件运行过程中,如果想停止插件运行,可以在 Progress 页面终止 EDAS-deploy 进程。

      Progress

      常见问题:应用列表获取不到应用

      通常出现这种情况为使用子账号来部署应用,且子账号没有同步到 EDAS 系统或者没有进行正确授权,从而导致在应用列表下拉框中看不到应用。您可以通过 RAM 授权或 EDAS 子账号授权来确保子账号已经同步到 EDAS 并且得到授权。

      RAM 授权

      该授权方式可使子账号访问 EDAS 的所有资源。

      1. RAM 控制台左侧导航栏中选择人员管理 > 用户
      2. 用户页面上找到需要授权的子用户,单击操作列中的添加权限
      3. 添加权限面板的选择权限区域中,搜索 AliyunEDASFullAccess 权限,单击权限策略将其添加至右侧的已选择列表中,然后单击确定
      4. 添加权限授权结果页面上,查看授权信息摘要,并单击完成
      5. 使用主账号登录 EDAS 控制台,在左侧导航栏选择系统管理 > 子账号,单击子账号页面右上角的同步子账号

      EDAS 子账号授权

      该授权方式可使子账号细粒度授权访问 EDAS 的资源。

      1. 使用主账号登录 EDAS 控制台
      2. 在左侧导航栏选择系统管理 > 角色,单击角色页面右上角的创建角色
      3. 输入一个角色名称,在可选权限区域框中,选择应用管理 > 应用列表 > 基本信息 > 部署应用,单击添加将部署应用角色添加到已选权限,然后单击确定
      4. 在左侧导航栏选择系统管理 > 子账号,单击子账号页面右上角的同步子账号
      5. 选择需进行授权的子账号,在操作列单击管理角色,在左侧搜索框中搜索并选择上面创建的角色,将该角色添加到右侧已选角色列表中,然后单击确定
      6. 选择需进行授权的子账号,在操作列单击授权应用,选择应用添加到右侧列表进行授权,然后单击确定

      问题反馈

      • 若您在使用 Cloud Toolkit 的过程中遇到问题,请首先查看常见问题并自助排查。

      • 若您需要进一步的帮助或想参与 Cloud Toolkit 的开发和升级,请加入我们的创造营。加入方式如下:

        • 使用钉钉扫描以下二维码,或搜索群号(30028976)加入钉钉群。钉钉群二维码4群
        • 使用微信扫描以下二维码,或搜索微信账号(nichao862)添加好友,待验证通过后产品经理将拉您进入微信群。微信二维码

      ]]> ALIYUN::ECS::LaunchTemplate_ECS_资源类型_资源编排-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ALIYUN::ECS::LaunchTemplate

      更新时间:2020-02-25 10:39:39

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      ALIYUN::ECS::LaunchTemplate类型可用于创建ECS实例启动模板。

      语法

      {
        "Type": "ALIYUN::ECS::LaunchTemplate",
        "Properties": {
          "LaunchTemplateName": String,
          "VersionDescription": String,
          "ImageId": String,
          "InstanceType": String,
          "SecurityGroupId": String,
          "NetworkType": String,
          "VSwitchId": String,
          "InstanceName": String,
          "Description": String,
          "InternetMaxBandwidthIn": Integer,
          "InternetMaxBandwidthOut": Integer,
          "HostName": String,
          "ZoneId": String,
          "SystemDiskCategory": String,
          "SystemDiskSize": Number,
          "SystemDiskDiskName": String,
          "SystemDiskDescription": String,
          "IoOptimized": String,
          "InternetChargeType": String,
          "UserData": String,
          "KeyPairName": String,
          "RamRoleName": String,
          "AutoReleaseTime": String,
          "SpotStrategy": String,
          "SpotPriceLimit": String,
          "SecurityEnhancementStrategy": String,
          "DiskMappings": List,
          "NetworkInterfaces": List,
          "Tags": List,
          "TemplateTags": List
        }
      }

      属性

      属性名称 类型 必须 允许更新 描述 约束
      LaunchTemplateName String 实例启动模板名称。 长度为[2, 128]个英文或中文字符。必须以大小字母或中文开头,不能以http://和https://开头。可以包含数字、半角冒号(:)、下划线(_)或者连字符(-)。
      VersionDescription String 实例启动模板版本1描述。 长度为[2, 256]个英文或中文字符,不能以http://和https://开头。
      ImageId String 镜像ID。
      InstanceType String 实例类型。
      SecurityGroupId String 安全组ID。
      NetworkType String 实例网络类型。

      取值范围:

      classic:经典网络。

      vpc:VPC网络。

      VSwitchId String 创建VPC类型实例时需要指定虚拟交换机ID。
      InstanceName String 实例名称。 长度为[2, 128]个英文或中文字符。必须以大小字母或中文开头,不能以http://和https://开头。可以包含数字、半角冒号(:)、下划线(_)或者连字符(-)。
      Description String 实例描述。 长度为[2, 256]个英文或中文字符,不能以http://和https://开头。
      InternetMaxBandwidthIn Integer 公网入带宽最大值,单位为Mbit/s。 取值范围:[1,200]。
      InternetMaxBandwidthOut Integer 公网出带宽最大值,单位为Mbit/s。 取值范围:[0, 100]。
      HostName String 实例主机名。

      点号(.)和短横线(-)不能作为首尾字符,更不能连续使用。

      Windows实例:

      字符长度为[2, 15],不支持点号(.),不能全是数字。允许大小写英文字母、数字和短横线(-)。

      其他类型实例(Linux等):

      字符长度为[2, 64],支持多个点号(.),点之间为一段,每段允许大小写英文字母、数字和短横线(-)。

      ZoneId String 实例所属的可用区ID。
      SystemDiskCategory String 系统盘的磁盘种类。 取值范围:

      cloud:普通云盘。

      cloud_efficiency:高效云盘。

      cloud_ssd:SSD云盘。

      ephemeral_ssd:本地SSD盘。

      SystemDiskSize Number 系统盘大小,单位为GiB。 取值范围:[20, 500]
      SystemDiskDiskName String 系统盘名称。 长度为[2, 128]个英文或中文字符。必须以大小字母或中文开头,不能以http://和https://开头。可以包含数字、半角冒号(:)、下划线(_)或者连字符(-)。
      SystemDiskDescription String 系统盘描述。 长度为[2, 256]个英文或中文字符,不能以http://和https://开头。
      IoOptimized String 是否为I/O优化实例。

      取值范围:

      none:非I/O优化。

      optimized:I/O优化。

      InternetChargeType String 网络付费类型。

      取值范围:

      PayByBandwidth:按带宽计费。

      PayByTraffic:按流量计费。

      UserData String 实例自定义数据。 需要以Base64方式编码,原始数据最多为16 KB。
      KeyPairName String 密钥对名称。 Windows实例,忽略该参数。默认为空。即使填写了该参数,仍旧只执行Password的内容。Linux实例的密码登录方式会被初始化成禁止。
      RamRoleName String 实例RAM角色名称。
      AutoReleaseTime String 实例自动释放时间。 按照ISO8601标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。
      SpotStrategy String 后付费实例的抢占策略。

      当创建实例接口中的InstanceChargeType参数取值为PostPaid时为生效。

      取值范围:

      NoSpot:正常按量付费实例。

      SpotWithPriceLimit:设置上限价格的抢占式实例。

      SpotAsPriceGo:系统自动出价,最高按量付费价格。

      SpotPriceLimit String 设置实例的每小时最高价格。 支持最多3位小数。
      SecurityEnhancementStrategy String 是否开启安全加固。

      取值范围:

      Active:启用。

      Deactive:不启用。

      DiskMappings List 数据盘列表。 最多16个。
      NetworkInterfaces List 弹性网卡列表。 最多8个。
      Tags List 实例、安全组、磁盘和网卡的标签列表 最多20个
      TemplateTags List 启动模板的标签列表。 最多20个。

      DiskMappings语法

      "DiskMappings": [
        {
          "Category": String,
          "DiskName": String,
          "Description": String,
          "SnapshotId": String,
          "Size": String,
          "Encrypted": String,
          "DeleteWithInstance": String
        }
      ]

      DiskMappings属性

      属性名称 类型 必须 允许更新 描述 约束
      Category String 数据盘的磁盘种类。

      取值范围:

      cloud:普通云盘。

      cloud_efficiency:高效云盘。

      cloud_ssd:SSD云盘。

      ephemeral_ssd:本地SSD盘。

      DiskName String 数据盘名称。 长度为[2, 128]个英文或中文字符。必须以大小字母或中文开头,不能以http://和https://开头。可以包含数字、半角冒号(:)、下划线(_)或者连字符(-)。
      Description String 数据盘描述。 长度为[2, 256]个英文或中文字符,不能以http://和https://开头。
      SnapshotId String 创建数据盘使用的快照。
      Size String 系统盘大小,单位为GiB。

      取值范围:

      cloud:[5, 2000]。

      cloud_efficiency:[20, 32768]。

      cloud_ssd:[20, 32768]。

      ephemeral_ssd:[5, 800]。

      Encrypted Boolean 是否加密数据盘。
      DeleteWithInstance Boolean 数据盘是否随实例释放而释放。

      NetworkInterfaces语法

      "NetworkInterfaces": [
        {
          "PrimaryIpAddress": String,
          "VSwitchId": String,
          "SecurityGroupId": String,
          "NetworkInterfaceName": String,
          "Description": String
        }
      ]

      NetworkInterfaces属性

      属性名称 类型 必须 允许更新 描述 约束
      PrimaryIpAddress String 弹性网卡的主私有IP地址。
      VSwitchId String 弹性网卡所属的虚拟交换机ID。
      SecurityGroupId String 弹性网卡所属的安全组ID。
      NetworkInterfaceName String 弹性网卡名称。
      Description String 弹性网卡描述信息。 长度为[2, 256]个英文或中文字符,不能以http://和https://开头。

      Tags语法

      "Tags": [
        {
          "Value": String,
          "Key": String
        }
      ]

      Tags属性

      属性名称 类型 必须 允许更新 描述 约束
      key String
      value String

      TemplateTags语法

      "TemplateTags": [
        {
          "Value": String,
          "Key": String
        }
      ]

      TemplateTags属性

      属性名称 类型 必须 允许更新 描述 约束
      key String
      value String

      返回值

      Fn::GetAtt
      • LaunchTemplateId: 实例启动模板ID。
      • LaunchTemplateName:实例启动模板名称。
      • DefaultVersionNumber:实例启动模板默认版本号。
      • LatestVersionNumber:实例启动模板最新版本号。

      示例

      {
        "ROSTemplateFormatVersion": "2015-09-01",
        "Resources": {
          "Template1": {
            "Type": "ALIYUN::ECS::LaunchTemplate",
            "Properties": {
              "LaunchTemplateName": "MyTemplate",
              "VersionDescription": "Launch template with all properties set",
              "ImageId": "m-2ze9uqi7wo61hwep5q52",
              "InstanceType": "ecs.n4.small",
              "SecurityGroupId": "sg-2ze8yxgempcdsq3iucsi",
              "NetworkType": "vpc",
              "VSwitchId": "vsw-2zei67xd9nhcqxzec7qt7",
              "InstanceName": "InstanceName",
              "Description": "Description of template",
              "InternetMaxBandwidthIn": 100,
              "InternetMaxBandwidthOut": 200,
              "HostName": "tttinfo",
              "ZoneId": "cn-beijing-a",
              "SystemDiskCategory": "cloud_ssd",
              "SystemDiskSize": "40",
              "SystemDiskDiskName": "TheSystemDiskName",
              "SystemDiskDescription": "The system disk description",
              "IoOptimized": "optimized",
              "InternetChargeType": "PayByBandwidth",
              "UserData": "dGhpcyBpcyBhIHVzZXIgZGF0YSBleG1hcGxl",
              "KeyPairName": "ThisIsKeyPair",
              "RamRoleName": "ThisIsRamRole",
              "AutoReleaseTime": "2050-10-01T00:00:00Z",
              "SpotStrategy": "SpotWithPriceLimit",
              "SpotPriceLimit": "100.001",
              "SecurityEnhancementStrategy": "Active",
              "DiskMappings": [
                {
                  "Category": "cloud_ssd",
                  "Size": 40,
                  "SnapshotId": "s-2ze1fr2bipove27bn9mz",
                  "Encrypted": true,
                  "DiskName": "dataDisk1",
                  "Description": "I am data disk 1",
                  "DeleteWithInstance": true
            },
            {
                  "Category": "cloud_efficiency",
                  "Size": 20,
                  "SnapshotId": "s-2ze4k0w8b33mlsqu4tl6",
                  "Encrypted": false,
                  "DiskName": "dataDisk2",
                  "Description": "I am data disk 2",
                  "DeleteWithInstance": true
             }
           ],
           "NetworkInterfaces": [
             {
                  "PrimaryIpAddress": "10.10.1.1",
                  "VSwitchId": "vsw-2zetgeiqlemyok9z5j2em",
                  "SecurityGroupId": "sg-2ze8yxgempcdsq3iucsi",
                  "NetworkInterfaceName": "my-eni1",
                  "Description": "My eni 1"
              },
           ],
           "Tags": [
                {
                  "Key": "key1",
                  "Value": "value1"
                },
                {
                  "Key": "key2",
                  "Value": "value2"
                }
               ],
           "TemplateTags": [
             {
                  "Key": "templateKey1",
                  "Value": "templateValue1"
                   },
                 {
                  "Key": "templateKey2",
                  "Value": "templateValue2"
                 }
             ]
             }
           }
        },
        "Outputs": {
            "LaunchTemplateId": {
                "Value": {"Fn::GetAtt": ["Template1","LaunchTemplateId"]}
          },
         "LaunchTemplateName": {
              "Value": {"Fn::GetAtt": ["Template1","LaunchTemplateName"]}
          },
         "DefaultVersionNumber": {
                "Value": {"Fn::GetAtt": ["Template1","DefaultVersionNumber"]}
          },
         "LatestVersionNumber": {
                "Value": {"Fn::GetAtt": ["Template1","LatestVersionNumber"]}
          }
        }
      }

      ]]> 云监控ECS的Windows主机CPU监控数值异常_技术分享_常见问题_云监控-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 云监控ECS的Windows主机CPU监控数值异常

      更新时间:2020-04-16 16:37:49

      编辑 · 

      新浪微博 微信 钉钉

      本页目录

      本文为您介绍云监控ECS的Windows主机CPU监控数值异常的原因及解决办法。

      云监控中的ECS CPU监控数值如果出现为0或者负数(实际CPU使用率不是0),其他监控值都正常。这个问题主要出现在Windows的机器上,一般原因是Windows内部的性能计数器损坏了。

      可以通过typeperf "Processor(_Total)% Processor Time"查看计数器是否正常。如果提示“错误:没有有效计数器”,则说明计数器已损坏,可通过lodctr /r命令进行修复。

      相关截图如下:查看计数器

      ]]> 禁用ECS的内网后不能使用云监控_技术分享_常见问题_云监控-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 禁用ECS的内网后不能使用云监控

      更新时间:2020-06-18 16:09:52

      编辑 · 

      新浪微博 微信 钉钉

      本页目录

      本文为您介绍为何禁用ECS的内网后不能使用云监控。

      ECS服务器使用云监控服务,是不能禁用内网的。

      因为云监控的通讯地址open.cms.aliyun.com是解析在内网上的,通过内网来进行通讯获取数据,如果禁用了内网,云监控服务会出现无法正常使用,所以为了能够正常的使用云监控服务,必须要确保在服务器上能连通open.cms.aliyun.com的80端口。Telnet

      ]]> 云虚拟主机可以升级ECS吗_升级/续费_产品定价_云虚拟主机-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 云虚拟主机可以升级ECS吗

      更新时间:2017-07-17 09:09:54

      本页目录

      云虚拟主机无法无缝升级到云服务器 ECS如果您需要将网站迁移到 ECS,需要您配置站点,将数据迁移到新的服务器上,将环境配置好后,将您的域名解析指向新服务器 IP

      如您自行迁移数据遇到困难,可到阿里云云市场联系网站迁移服务商进行咨询。

      ]]> 如何安装 ECS 云助手?_常见问题_Alibaba Cloud Toolkit-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 如何安装 ECS 云助手?

      更新时间:2019-09-11 13:52:47

      本页目录

      若您在使用 Cloud Toolkit 部署应用时出现以下报错,可能原因是您的 ECS 实例没有安装云助手客户端。 error

      2017 年 12 月 01 日之后使用公共镜像创建的ECS实例,默认预装云助手客户端。如果您的实例是 2017 年 12 月 01 日之前购买的,若需要使用 Cloud Toolkit,则需自行安装云助手客户端。

      解决方案如下:

      1. 安装云助手客户端,请参见云助手客户端
      2. 启动云助手客户端,请参见停止或启动客户端

      ]]> Java应用部署到ECS_Java入门_快速入门_云效-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 Java应用部署到ECS

      更新时间:2020-03-18 10:03:05

      本页目录

      本文档会帮助您在云效创建一个 Java Spring Boot 的代码库,并部署到阿里云 ECS 服务器。

      创建企业

      首次进入云效,会提示您创建企业。输入企业名称,点击【立即创建】。

      java-ecs-1

      创建流水线

      1. 进入企业后,从页面顶栏点击【研发】->【流水线】,进入流水线列表。

        java-ecs-2

      2. 点击右上角【新建流水线】,进入流水线创建向导页面。

        java-ecs-3

      新建代码库

      1. 在源码设置页面中,选择阿里云Code,点击代码仓库右侧【新建代码库】。

        java-ecs-4

      2. 在弹窗中选择或创建代码组,输入新建代码仓库名称,点击【下一步】。

        java-ecs-5

      3. 在代码模板中选择 Java 和 spring-boot ,取消勾选【生成Dockerfile】,点击【确认】。您可以点击代码模板图标下的预览按钮查看即将生成的代码库文件内容。

        java-ecs-6

      4. 代码库创建完成后会恢复到代码库选择页面并回填刚才创建代码库的信息,为了在代码提交时候触发持续集成,打开【开启监听】,然后点击【下一步】。

        java-ecs-7

      编辑流水线

      1. 云效会识别代码库语言并推荐相应流水线模板,使用默认置顶选中的【Java构建,测试,部署到主机】流水线模板,然后点击【创建】。

        java-ecs-8

      2. 填写流水线名称,点击【下一步】。

        java-ecs-9

      3. 部署配置。点击阶段【部署】阶段进行部署配置,在任务列表中选择【主机部署】,选择由构建环节产出的待部署【制品】。

        java-ecs-10

      4. 新建应用。在【应用】下拉框中选择【新建应用】填写应用名称,点击【确认】。

        java-ecs-11

      5. 新建环境。在【环境】下拉框中选择【新建环境】,填写环境名称,点击【机器配置】。

        java-ecs-12

      6. 机器配置。点击【使用临时模板机器】,稍等片刻会获得一台用于测试的机器。

        java-ecs-13java-ecs-14

      7. 获取临时机器成功后点击【刷新】按钮,会在机器配置页面左边看到刚才创建的机器。选中并添加到右侧,点击【确认】完成环境创建。

        java-ecs-15java-ecs-16

      运行流水线

      1. 点击右上角的【运行】,即可保存并触发流水线。

        java-ecs-17

      2. 运行成功。

        java-ecs-18

      查看测试报告

      1. Java 规约扫描。点击阶段里的点击链接可以查看报告预览,点击预览中的【链接】可以查看完整报告。

        java-ecs-19

      2. 点击测试构建阶段里的【更多】,通过【版本信息】可以查看和下载用于部署的软件包,点击【单元测试】可以展开测试结果预览,点击预览里的【链接】可以查看测试报告详情。

        java-ecs-20

      查看部署结果

      1. 点击【部署详情】可以查看部署单。在部署单中可以查看每台机器的部署情况和日志。

        java-ecs-21

      2. 访问 ECS 的公网 IP 的 8080 端口,可以看到服务运行起来了。

        java-ecs-22

      ]]> ALIYUN::ECS::Invocation_ECS_资源类型_资源编排-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ALIYUN::ECS::Invocation

      更新时间:2019-06-24 11:45:02

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      ALIYUN::ECS::Invocation类型用于为一台或多台ECS实例触发一条云助手命令。

      语法

      {
        "Type": "ALIYUN::ECS::Invocation",
        "Properties": {
          "Timed": Boolean,
          "Frequency": String,
          "CommandId": String,
          "InstanceIds": List
        }
      }

      属性

      属性名称 类型 必须 允许更新 描述 约束
      Timed Boolean 命令是否为周期执行。默认值:False
      Frequency String 周期任务的执行周期。该参数值结构以 Cron 表达式 为准。
      CommandId String 命令 ID。
      InstanceIds List 执行命令的实例列表。最多能指定20台实例ID。

      返回值

      Fn::GetAtt

      • InvokeId: 命令进程执行 ID。

      示例

      {
        "ROSTemplateFormatVersion": "2015-09-01",
        "Parameters": {
          "Timed": {
            "Type": "Boolean",
            "Description": "Whether it is timed execution. Default is False.",
            "AllowedValues": [
              "True",
              "true",
              "False",
              "false"
            ]
          },
          "Frequency": {
            "Type": "String",
            "Description": "The frequency of timing execution (the shortest frequency is performed every 1 minute). It iss mandatory when Timing is True.The value rule follows the rules of the cron expression. "
          },
          "CommandId": {
            "Type": "String",
            "Description": "The id of command."
          },
          "InstanceIds": {
            "Type": "CommaDelimitedList",
            "Description": "The instance id list. Select up to 20 instances at a time.Instances selected network type must be VPC network, status must be running"
          }
        },
        "Resources": {
          "Invocation": {
            "Type": "ALIYUN::ECS::Invocation",
            "Properties": {
              "Timed": {
                "Ref": "Timed"
              },
              "Frequency": {
                "Ref": "Frequency"
              },
              "CommandId": {
                "Ref": "CommandId"
              },
              "InstanceIds": {
                "Fn::Split": [
                  ",",
                  {
                    "Ref": "InstanceIds"
                  },
                  {
                    "Ref": "InstanceIds"
                  }
                ]
              }
            }
          }
        },
        "Outputs": {
          "InvokeId": {
            "Description": "The id of command execution.",
            "Value": {
              "Fn::GetAtt": [
                "Invocation",
                "InvokeId"
              ]
            }
          }
        }
      }

      ]]> 将已绑定EIP的ECS实例加入共享带宽_最佳实践_共享带宽-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 将已绑定EIP的ECS实例加入共享带宽

      更新时间:2019-09-27 14:44:03

      本页目录

      您可以将已绑定EIP的ECS实例加入共享带宽中,复用共享带宽中的带宽

      前提条件

      操作前,请确保满足以下条件:
      • 您已经注册了阿里云账号。如还未注册,请先完成账号注册。详细信息,请参见账号注册
      • 您已经创建了共享带宽实例。详细信息,请参见创建共享带宽实例

      操作步骤

      1. 登录专有网络管理控制台
      2. 在左侧导航栏,单击共享带宽
      3. 共享带宽页面,找到目标共享带宽实例,单击操作列下的添加IP
      4. 添加IP页面,单击从已有EIP列表选取页签。
      5. 选择已绑定ECS实例的EIP,然后单击确定

        说明 EIP添加到共享带宽后,EIP的带宽峰值将变为共享带宽的带宽峰值。同时EIP会停止之前的计费,按流量计费的EIP不再收取流量费用,按带宽计费的EIP不再收取带宽费用。

      ]]> ECS实例与MongoDB实例网络类型不同时如何连接_连接实例_用户指南_云数据库 MongoDB-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ECS实例与MongoDB实例网络类型不同时如何连接

      更新时间:2019-10-29 14:23:49

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      当ECS实例是经典网络而MongoDB实例是专有网络(VPC),或者MongDB实例是经典网络而ECS实例是专有网络,您可根据本文中的办法快速实现不同网络类型的ECS实例连接至MongoDB实例的需求。

      前提条件

      • ECS实例和MongoDB实例在同一阿里云账号中,且属于同一地域。
      • 已将ECS实例的IP地址加入MongoDB实例的白名单中,详情请参见设置白名单

        说明 关于获取ECS实例IP地址信息,请参见如何查询ECS实例的IP地址

      经典网络的ECS实例连接专有网络的MongoDB实例

      经典网络的ECS连接专有网络的MongoDB

      通过下述三种方法均可以实现经典网络的ECS实例连接专有网络的MongoDB实例,您可以根据业务规划自行选择。

      • 将ECS实例迁移至MongoDB实例所属的专有网络中,详情请参见将ECS实例迁移至专有网络
      • 将MongoDB实例的网络类型切换为经典网络,详情请参见从专有网络切换为经典网络
      • 使用ClassicLink实现互通。

        说明 基于ClassicLink互访方案为特殊情况下的临时解决方案,生产环境中为了实现高速连接,建议您将ECS实例和MongoDB实例创建在同一VPC网络内。

        在建立ClassicLink前确保您已经了解建立连接的限制,详情请参见ClassicLink

        开启ClassicLink操作步骤:

        1. 登录专有网络管理控制台
        2. 选择目标专有网络的地域,然后单击目标专有网络的ID。
        3. 专有网络详情页面,单击开启ClassicLink, 然后在弹出的对话框,单击确定
        4. 登录ECS管理控制台
        5. 在左侧导航栏,单击目标实例
        6. 在页面左上角选择实例的所属地域。
        7. 在目标ECS实例(经典网络)的操作列中,单击更多 > 网络和安全组 > 设置专有网络连接状态
        8. 在弹出的对话框中选择MongoDB实例所属的专有网络,单击确定
        9. 在新弹出的连接专有网络对话框中,单击前往实例安全组列表添加classicLink安全组规则经典网络的ECS连接专有网络
        10. 单击添加ClassicLink安全组规则,根据以下信息配置ClassicLink安全组规则,然后单击确定
          配置 说明
          经典网络安全组 显示经典网络安全组的名称。
          选择专有网络安全组 选择专有网络的安全组。
          授权方式 选择一种授权方式:
          • 经典网络 <=> 专有网络:相互授权访问,推荐使用这种授权方式。
          • 经典网络 => 专有网络:授权经典网络ECS访问专有网络内的云资源。
          • 专有网络 => 经典网络:授权专有网络内的云资源访问经典网络ECS。
          协议类型 选择授权通信的协议和端口。
          端口范围 端口的输入格式为xx/xx,此处放通的端口为MongoDB实例的端口3717,填入3717/3717
          优先级 设置该规则的优先级。数字越小,优先级越高。
          描述 填入安全组描述,长度为2-256个字符,不能以 http:// 或 https:// 开头。

      专有网络的ECS实例连接经典网络的MongDB实例

      专有网络的ECS连接经典网络的MongoDB

      将MongoDB实例切换到ECS实例所属的专有网络中,详情请参见从经典网络切换为专有网络

      说明

      • 单节点实例暂不支持切换网络类型。
      • 切换网络时,实例将会出现一次闪断。请您尽量在业务低峰期执行切换操作,或确保您的应用有自动重连机制,以避免闪断造成的影响。

      ]]> 扩容部署在 ECS 集群中的应用_ECS 集群应用生命周期管理_应用管理_ECS集群用户指南_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 扩容部署在 ECS 集群中的应用

      更新时间:2019-12-11 17:37:38

      本页目录

      当部署应用的 ECS 实例负载过高或者应用内没有实例时,可以通过添加实例来扩容。现EDAS 支持三种扩容方式:从该应用部署的集群选择、基于现有实例规格购买和基于实例启动模板购买。

      扩容实例的运行状态说明

      扩容的实例的运行状态是根据所在应用的运行状态而定的。

      • 如果扩容的时候应用已经部署并处于运行状态,那么扩容的实例会自动部署应用、启动、运行应用。
      • 如果扩容的时候应用已经部署并处于停止状态,那么扩容的实例仍然会自动部署应用、启动、运行应用。
      • 如果扩容的时候应用实例未部署,那么扩容的实例不会自动部署,也不会启动、运行应用,扩容的实例会处于停止状态。

      进入应用扩容功能

      1. 在应用详情页面右上角单击应用扩容。在购买实例对话框的扩容方式页签内选择扩容的目标分组
      2. 选择扩容方式,并分别参照以下扩容方式完成后续扩容步骤。

      从该应用部署的集群选择

      该扩容方式是从应用所在集群内选择空闲的实例添加到应用来实现扩容。

      1. 扩容方式勾选从该应用部署的集群选择
      2. 在实例列表中勾选用于扩容的空闲实例,然后单击扩容

        从该应用部署的集群选择

        页面上出现扩容成功的提示后,进入应用实例部署信息页面,查看扩容实例的运行状态,如果显示运行正常则说明扩容成功。

      基于现有实例规格购买

      该扩容方式是以所在集群内的任意一个实例作为规格模板,来配置购买实例实现扩容。

      1. 扩容方式勾选基于现有实例规格购买,该页面内会罗列出应用所在集群内的所有实例。
      2. 勾选您想作为规格模板的实例,选择回收模式,然后单击下一步

        说明 此次 ECS 实例代购默认会复制所选择 ECS 实例的规格、磁盘、网络、userdata、标签等基本信息。

        基于现有实例规格购买

      3. 购买信息页面,配置购买数量登录密钥,并勾选《云服务器 ECS 服务条款》 | 《镜像商品使用条款》,然后单击下一步
      4. 确认扩容页面查看代购的实例信息,然后单击确认扩容

        页面上方会出现已触发自动购买的流程,请查看变更流程获取实时信息的提示。

      5. 进入应用的实例部署信息页面,查看扩容实例的运行状态,如果显示运行正常则说明扩容成功。

      说明

      • 基于现有 ECS 规格购买机器分为两个变更流程进行,第一个变更流程是 EDAS 从 ECS 为您代购 ECS实例;第二个变更流程是将代购后的 ECS 实例自动扩容进应用中。
      • 从您提交请求到应用开始扩容所需实例,前后约三分钟;两个变更流程执行相隔约10秒。
      • 所有计费信息均按照 ECS 与 EDAS 的正常计费进行,此次操作不会产生额外的费用。
      • ECS 中默认的登录信息均以您自行设定的 KeyPair 进行,EDAS 不会触碰您的任何私密信息。

      基于实例启动模板购买

      使用实例启动模板购买需您提前在 ECS 控制台创建实例启动模板,然后在 EDAS 控制台上基于您创建的模板来购买实例。创建启动模板的具体操作请参见创建实例启动模板

      注意 您所创建的启动模板必须和您的应用在同一个 VPC 内,否则所创建的启动模板无法被有效选择。

      1. 扩容方式勾选基于实例启动模板购买
      2. 选择绑定模板和模板版本,选择回收模式,然后单击下一步
        • 使用绑定模板:在实例分组中绑定的启动模板,具体文档请参见绑定实例启动模板
        • 使用指定模板:在 ECS 控制台创建的模板。如果您创建过多个模板,需要选择具体模板版本
      3. 购买信息页面选择购买数量并勾选《《云服务器 ECS 服务条款》 | 《镜像商品使用条款》,然后单击下一步
      4. 确认扩容页面,检查需要购买的 ECS 数量和启动模板信息。确认无误后,单击确认扩容

        页面上方会出现已触发自动购买的流程,请查看变更流程获取实时信息的提示。

      5. 进入应用的实例部署信息页面,查看扩容实例的运行状态,如果显示运行正常则说明扩容成功。

      更多信息

      ]]> 创建ECS实例_ECS_资源管理_ECS集群用户指南_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 创建ECS实例

      更新时间:2020-06-01 14:35:02

      本页目录

      如果您还没有ECS实例,请参考本文档创建ECS实例,以便部署应用。

      前提条件

      在创建ECS实例前,请先确认您要创建实例的网络:
      • 如果您在2017年6月14日17:00 (UTC+8)之后,第一次购买ECS实例,不能再选择经典网络,只能选择专有网络(VPC)。在此之前创建的经典网络的ECS实例可以继续使用。
      • 在VPC创建ECS实例,需要选择VPC,且选择VPC之后,该ECS实例不能再转移到其它VPC中。所以请确认您要创建ECS实例的VPC。如果没有VPC,请先创建VPC,具体操作步骤请参见搭建IPv4专有网络

      操作步骤

      1. 登录EDAS控制台
      2. 在左侧导航栏中选择资源管理 > ECS
      3. ECS页面选择您要创建ECS实例的地域命名空间(可选),在页面右上角单击创建实例
      4. 在ECS购买页面,根据您的需要,参考创建ECS实例,完成ECS的规格配置和支付。

        注意 在EDAS控制台使用和管理ECS实例需要在ECS实例上安装EDAS Agent。在购买ECS实例时,通过选择EDAS基础镜像自动安装EDAS Agent。

        1. 镜像一栏中,选择镜像市场,然后单击从镜像市场获取更多选择(含操作系统)
        2. 在镜像市场对话框的搜索框中输入EDAS,单击搜索
        3. 在搜索结果中选择EDAS JAVA环境(普通ECS)版本(默认选择当前最新版本)和单击使用

      结果验证

      单击管理控制台返回云服务器管理控制台,一般需要1~5分钟完成实例创建。单击刷新按钮,新建的ECS实例状态变为运行中,表示实例创建成功。

      ]]> 金融云ECS云服务器互联网互通说明_常见问题_产品常见问题_金融云-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 金融云ECS云服务器互联网互通说明

      更新时间:2018-01-03 11:24:41

      本页目录

      一、适用范围

      本说明仅仅针对阿里金融云,不适用于公共云或其它行业云。


      二、综述

      如下图所示,ECS主动访问互联网和被动接受互联网访问,两条数据通道彼此隔离,互相不重叠,不交叉,计量和收费亦独立进行。

      如ECS云服务器没有主动访问互联网的需求,无需购买公网带宽。反之,如有访问需求,则必须购买公网带宽,否则无法主动发起互联网访问。

      因为金融行业的安全合规特殊性,目前经典网络限制互联网直接访问ECS任何端口,VPC的弹性公网IP限制2W以下敏感端口。如ECS云服务网需要对互联网提供服务,建议购买SLB(负载均衡),然后进入ECS云服务器的内网IP(端口)实现。

      SLB(负载均衡)创建后,默认ECS所在安全组对SLB(负载均衡)内网IP地址段开放访问权限,无需单独配置。

      安全说明:金融云目前的安全策略由外部网络设备控制,但从安全实践出发我们还是建议用户在ECS上做好安全组的配置,在安全组上禁止公网测不合理的访问需求,以保证多层的安全防护体系。

       

      三、数据通道图示


      图1:



      四、ECS云服务器主动访问互联网

      1)数据通道

      见图1右侧:ECS云服务器公网IP(vpc中就是弹性公网IP)———->防火墙———->互联网server,然后数据沿原路返回。

      2)计费方式

      该通道可以采用按带宽计费或按照固定带宽计费模式。

       

      五、ECS云服务器提供web服务给互联网用户

      1)数据通道

      见图1左侧:互联网用户———->SLB(负载均衡)———->ECS云服务器内网IP,然后数据沿原路返回。

      2)计费方式

      该通道可以采用按带宽计费或按照固定带宽计费模式。

       

      六、是否存在重复计费?

      不存在。因为, ECS公网IP和SLB,都只按照流量的下行方向计费,即从ECS往外的流量计费。

      例:某ECS往外的流量是5M,其中2M是ECS主动访问互联网所产生,3M是互联网用户访问ECS,ECS回包所产生。无论是都通过ECS公网IP进出(图3),还是部分走SLB、部分走ECS 公网IP(图2),总的下行流量5M不会变。

       

      图2:


      图3:

       

      七、SLB(负载均衡)相关

      购买入口

      http://buy.aliyun.com/SLB

      帮助中心SLB(负载均衡)入口

      http://help.aliyun.com/all/11108234.html

       



      ]]> 跨账号同步ECS主机名_用户指南_云解析 PrivateZone-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 跨账号同步ECS主机名

      更新时间:2020-03-17 14:28:58

      本页目录

      什么是同步ECS主机名

      同步ECS主机名,是指通过PrivateZone自动获取ECS服务器实例信息中的主机名配置,然后根据ECS主机名在privateZone中一键生成相应的内网DNS解析记录(即主机名记录)。

      什么是跨账号同步ECS主机名

      跨账号同步ECS主机名是指,例如有阿里云账号A和B时,可实现将B账号下的ECS主机名同步到A账号下。

      适用场景

      当企业有多个阿里云账号,如果ECS分布在不同的账号,在使用PrivateZone管理内网DNS时,需要用户在每个账号下都创建一遍相同Zone名称、并操作ECS主机名同步,来生成主机名记录。这种管理操作繁琐不便且影响运维人员的工作效率,基于上述场景,我们为用户提供了PrivateZone可以跨账号同步ECS主机名功能。

      设置方法

      示例:以有阿里云账号A和账号B,期望结果是需要把账号B下的ECS主机名同步到账号A下。

      配置RAM角色

      在使用跨账号同步ECS主机名前,需要在B账号下先配置RAM角色,如未配置,则无法正常使用跨账号同步ECS功能。

      1、用 B账号 登录阿里云 RAM产品控制台

      2、菜单选择 RAM角色管理,单击 新建RAM角色 按钮,选择类型 请选中 阿里云服务,并点击 下一步;参考 示例图1

      3、配置角色 的角色名称请按规则输入 AssumeRoleFor12345,其中12345是示例,代表账号B的Uid(适用主账号)。选择授信服务 请选择 云服务器,并单击 完成,参考 示例图2

      4、 单击 为角色授权按钮,在系统权限策略中,检索 ECS,选中 AliyunECSReadOnlyAccess, 点击 确定,参考 示例图3

      5、返回RAM角色列表页,点击角色 AssumeRoleFor12345 ,选中 信任策略管理 ,单击 修改信任策略 按钮。请修改 Service,将里面的ecs.aliyuncs.com替换为账号A(适用主账号),输入规则为 Uid@pvtz.aliyuncs.com 。例如账号 AUid 是 345678,那么 sevice 就修改为 345678@pvtz.aliyuncs.com。参考示例图4

      示例图1

      新建RAM角色按钮


      选择阿里云服务


      示例图2

      示例图2


      示例图3

      为角色授权


      添加权限

      示例图4

      修改信任策略按钮
      修改策略

      跨账号同步ECS主机名

      1、 用 A账号 登录 云解析DNS控制台, 单击左侧菜单 PrivateZone

      2、 单击 添加zone,输入自定义的私有域名。

      添加zone

      3、 单击 zone名称,进入解析设置页面,选择 主机名记录 页签,单击 自动同步配置 按钮,选择当前账号下的 ECS区域做同步。

      同步主机名

      自动同步配置

      4、 如需同步 B账号下的 ECS主机名,需要先单击 添加账户

      添加账号

      5、 在添加账户的弹出框中,输入B账号的登录邮箱或用户名;单击发送验证码,验证码会同时发送到账号B的邮箱和手机中。

      添加账户

      6、验证通过后,在选择账号的下拉框中选中 账号B,然后选择需要同步主机名的ECS区域,点击确认,即完成跨账号同步ECS主机名。

      选择关联账号

      7、最后返回PrivateZone列表页,单击 关联VPC,选择账号下默认显示为当前账号下的VPC列表,完成 关联VPC

      关联VPC

      ]]> ECS小时数据_计量域_阿里云交易和账单管理API-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ECS小时数据

      更新时间:2019-07-08 09:10:36

      本页目录

      接口名

      QueryUserOmsData

      描述

      查询ECS产品按小时计量的数据信息。

      请求参数

      名称 类型 是否必填 说明
      Table String ecs
      DataType String 计量数据时间类型。Hour:小时
      StartTime String 请求的开始时间。日期格式按照ISO8601标准标示,并需要使用UTC时间。格式:yyyy-mm-ddThh:mm:ssZ
      EndTime String 请求的结束时间。日期格式按照ISO8601标准标示,并需要使用UTC时间。格式:yyyy-mm-ddThh:mm:ssZ
      Maker String 设定结果从NextToken之后按字母排序的第一个开始返回。默认从头返回
      PageSize Integer 分页查询每页行数,1~200,默认100行

      返回参数

      名称 类型 说明
      HostId String 用户所属站点,cn为国内站
      CPUs Integer 用户购买的CPU Core数量
      InstanceId String 实例ID
      OsDiskSize Integer 用户购买的OS盘大小,单位Bytes
      ImageOS String 镜像OS类型
      NetworkIn Integer 在StartTime和EndTime时间段内发生的从外部网络流入ECS的数据量,单位Bytes
      Disk Integer 用户购买的数据盘容量,单位Bytes
      NetworkOut Integer 在StartTime和EndTime时间段内发生的从ECS流出到外部网络的数据量,单位Bytes
      InstanceTypeName String 实例类型名称
      DataDiskSize Integer 数据盘大小,单位:GB
      IoOptimized Integer 创建的 ECS 是否 I/O 优化
      ImageSize Integer 镜像大小
      Memory Integer 用户购买的内存容量,单位Bytes
      Region String 地域
      OsDiskType String 用户购买的OS盘类型
      TmpDataDiskSize Integer 用户购买的本地磁盘容量,单位Bytes
      ProviderId String 产品供应商对应编号,默认26842
      SsdDataDiskSize Integer 用户购买的本地ssd磁盘容量,单位Bytes
      CloudDataDiskSize Integer 用户购买的云磁盘容量,单位Bytes
      Bandwidth Integer 用户购买的带宽,单位bps
      EndTime String 计费数据发生结束时间
      StartTime String 计费数据发生开始时间

      请求示例

      示例(按小时):

      1. https://business.aliyuncs.com/? Action=QueryUserOmsData
      2. &DataType=Hour
      3. &EndTime=2019-04-18T09:15:54Z
      4. &StartTime=2019-01-01T09:15:54Z
      5. &Table=ecs
      6. &<公共请求参数>

      返回示例

      示例(按小时):

      1. {
      2. "Data": {
      3. "OmsData": [
      4. {
      5. "CPUs": "1",
      6. "InstanceId": "i-bp1hmito9v058zbua0eb",
      7. "OsDiskSize": "42949672960",
      8. "NetworkIn": "0",
      9. "NetworkOut": "0",
      10. "ImageOS": "Windows Server 2016",
      11. "Disk": "0",
      12. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      13. "DataDiskSize": "0",
      14. "IoOptimized": "true",
      15. "ImageSize": "42949672960",
      16. "Memory": "536870912",
      17. "Region": "cn-hangzhou-dg-a01",
      18. "OsDiskType": "cloud_efficiency",
      19. "TmpDataDiskSize": "0",
      20. "ProviderId": "26842",
      21. "EndTime": "2019-01-01T11:00:00Z",
      22. "SsdDataDiskSize": "0",
      23. "StartTime": "2019-01-01T10:00:00Z",
      24. "CloudDataDiskSize": "0",
      25. "Bandwidth": "1048576"
      26. },
      27. {
      28. "CPUs": "1",
      29. "InstanceId": "i-bp1hmito9v058zbua0eb",
      30. "OsDiskSize": "42949672960",
      31. "NetworkIn": "0",
      32. "NetworkOut": "0",
      33. "ImageOS": "Windows Server 2016",
      34. "Disk": "0",
      35. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      36. "DataDiskSize": "0",
      37. "IoOptimized": "true",
      38. "ImageSize": "42949672960",
      39. "Memory": "536870912",
      40. "Region": "cn-hangzhou-dg-a01",
      41. "OsDiskType": "cloud_efficiency",
      42. "TmpDataDiskSize": "0",
      43. "ProviderId": "26842",
      44. "EndTime": "2019-01-01T12:00:00Z",
      45. "SsdDataDiskSize": "0",
      46. "StartTime": "2019-01-01T11:00:00Z",
      47. "CloudDataDiskSize": "0",
      48. "Bandwidth": "1048576"
      49. },
      50. {
      51. "CPUs": "1",
      52. "InstanceId": "i-bp1hmito9v058zbua0eb",
      53. "OsDiskSize": "42949672960",
      54. "NetworkIn": "0",
      55. "NetworkOut": "0",
      56. "ImageOS": "Windows Server 2016",
      57. "Disk": "0",
      58. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      59. "DataDiskSize": "0",
      60. "IoOptimized": "true",
      61. "ImageSize": "42949672960",
      62. "Memory": "536870912",
      63. "Region": "cn-hangzhou-dg-a01",
      64. "OsDiskType": "cloud_efficiency",
      65. "TmpDataDiskSize": "0",
      66. "ProviderId": "26842",
      67. "EndTime": "2019-01-01T13:00:00Z",
      68. "SsdDataDiskSize": "0",
      69. "StartTime": "2019-01-01T12:00:00Z",
      70. "CloudDataDiskSize": "0",
      71. "Bandwidth": "1048576"
      72. },
      73. {
      74. "CPUs": "1",
      75. "InstanceId": "i-bp1hmito9v058zbua0eb",
      76. "OsDiskSize": "42949672960",
      77. "NetworkIn": "0",
      78. "NetworkOut": "0",
      79. "ImageOS": "Windows Server 2016",
      80. "Disk": "0",
      81. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      82. "DataDiskSize": "0",
      83. "IoOptimized": "true",
      84. "ImageSize": "42949672960",
      85. "Memory": "536870912",
      86. "Region": "cn-hangzhou-dg-a01",
      87. "OsDiskType": "cloud_efficiency",
      88. "TmpDataDiskSize": "0",
      89. "ProviderId": "26842",
      90. "EndTime": "2019-01-01T14:00:00Z",
      91. "SsdDataDiskSize": "0",
      92. "StartTime": "2019-01-01T13:00:00Z",
      93. "CloudDataDiskSize": "0",
      94. "Bandwidth": "1048576"
      95. },
      96. {
      97. "CPUs": "1",
      98. "InstanceId": "i-bp1hmito9v058zbua0eb",
      99. "OsDiskSize": "42949672960",
      100. "NetworkIn": "0",
      101. "NetworkOut": "0",
      102. "ImageOS": "Windows Server 2016",
      103. "Disk": "0",
      104. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      105. "DataDiskSize": "0",
      106. "IoOptimized": "true",
      107. "ImageSize": "42949672960",
      108. "Memory": "536870912",
      109. "Region": "cn-hangzhou-dg-a01",
      110. "OsDiskType": "cloud_efficiency",
      111. "TmpDataDiskSize": "0",
      112. "ProviderId": "26842",
      113. "EndTime": "2019-01-01T15:00:00Z",
      114. "SsdDataDiskSize": "0",
      115. "StartTime": "2019-01-01T14:00:00Z",
      116. "CloudDataDiskSize": "0",
      117. "Bandwidth": "1048576"
      118. },
      119. {
      120. "CPUs": "1",
      121. "InstanceId": "i-bp1hmito9v058zbua0eb",
      122. "OsDiskSize": "42949672960",
      123. "NetworkIn": "0",
      124. "NetworkOut": "0",
      125. "ImageOS": "Windows Server 2016",
      126. "Disk": "0",
      127. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      128. "DataDiskSize": "0",
      129. "IoOptimized": "true",
      130. "ImageSize": "42949672960",
      131. "Memory": "536870912",
      132. "Region": "cn-hangzhou-dg-a01",
      133. "OsDiskType": "cloud_efficiency",
      134. "TmpDataDiskSize": "0",
      135. "ProviderId": "26842",
      136. "EndTime": "2019-01-01T16:00:00Z",
      137. "SsdDataDiskSize": "0",
      138. "StartTime": "2019-01-01T15:00:00Z",
      139. "CloudDataDiskSize": "0",
      140. "Bandwidth": "1048576"
      141. },
      142. {
      143. "CPUs": "1",
      144. "InstanceId": "i-bp1hmito9v058zbua0eb",
      145. "OsDiskSize": "42949672960",
      146. "NetworkIn": "0",
      147. "NetworkOut": "0",
      148. "ImageOS": "Windows Server 2016",
      149. "Disk": "0",
      150. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      151. "DataDiskSize": "0",
      152. "IoOptimized": "true",
      153. "ImageSize": "42949672960",
      154. "Memory": "536870912",
      155. "Region": "cn-hangzhou-dg-a01",
      156. "OsDiskType": "cloud_efficiency",
      157. "TmpDataDiskSize": "0",
      158. "ProviderId": "26842",
      159. "EndTime": "2019-01-01T17:00:00Z",
      160. "SsdDataDiskSize": "0",
      161. "StartTime": "2019-01-01T16:00:00Z",
      162. "CloudDataDiskSize": "0",
      163. "Bandwidth": "1048576"
      164. },
      165. {
      166. "CPUs": "1",
      167. "InstanceId": "i-bp1hmito9v058zbua0eb",
      168. "OsDiskSize": "42949672960",
      169. "NetworkIn": "0",
      170. "NetworkOut": "0",
      171. "ImageOS": "Windows Server 2016",
      172. "Disk": "0",
      173. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      174. "DataDiskSize": "0",
      175. "IoOptimized": "true",
      176. "ImageSize": "42949672960",
      177. "Memory": "536870912",
      178. "Region": "cn-hangzhou-dg-a01",
      179. "OsDiskType": "cloud_efficiency",
      180. "TmpDataDiskSize": "0",
      181. "ProviderId": "26842",
      182. "EndTime": "2019-01-01T18:00:00Z",
      183. "SsdDataDiskSize": "0",
      184. "StartTime": "2019-01-01T17:00:00Z",
      185. "CloudDataDiskSize": "0",
      186. "Bandwidth": "1048576"
      187. },
      188. {
      189. "CPUs": "1",
      190. "InstanceId": "i-bp1hmito9v058zbua0eb",
      191. "OsDiskSize": "42949672960",
      192. "NetworkIn": "0",
      193. "NetworkOut": "0",
      194. "ImageOS": "Windows Server 2016",
      195. "Disk": "0",
      196. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      197. "DataDiskSize": "0",
      198. "IoOptimized": "true",
      199. "ImageSize": "42949672960",
      200. "Memory": "536870912",
      201. "Region": "cn-hangzhou-dg-a01",
      202. "OsDiskType": "cloud_efficiency",
      203. "TmpDataDiskSize": "0",
      204. "ProviderId": "26842",
      205. "EndTime": "2019-01-01T19:00:00Z",
      206. "SsdDataDiskSize": "0",
      207. "StartTime": "2019-01-01T18:00:00Z",
      208. "CloudDataDiskSize": "0",
      209. "Bandwidth": "1048576"
      210. },
      211. {
      212. "CPUs": "1",
      213. "InstanceId": "i-bp1hmito9v058zbua0eb",
      214. "OsDiskSize": "42949672960",
      215. "NetworkIn": "0",
      216. "NetworkOut": "0",
      217. "ImageOS": "Windows Server 2016",
      218. "Disk": "0",
      219. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      220. "DataDiskSize": "0",
      221. "IoOptimized": "true",
      222. "ImageSize": "42949672960",
      223. "Memory": "536870912",
      224. "Region": "cn-hangzhou-dg-a01",
      225. "OsDiskType": "cloud_efficiency",
      226. "TmpDataDiskSize": "0",
      227. "ProviderId": "26842",
      228. "EndTime": "2019-01-01T20:00:00Z",
      229. "SsdDataDiskSize": "0",
      230. "StartTime": "2019-01-01T19:00:00Z",
      231. "CloudDataDiskSize": "0",
      232. "Bandwidth": "1048576"
      233. },
      234. {
      235. "CPUs": "1",
      236. "InstanceId": "i-bp1hmito9v058zbua0eb",
      237. "OsDiskSize": "42949672960",
      238. "NetworkIn": "0",
      239. "NetworkOut": "0",
      240. "ImageOS": "Windows Server 2016",
      241. "Disk": "0",
      242. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      243. "DataDiskSize": "0",
      244. "IoOptimized": "true",
      245. "ImageSize": "42949672960",
      246. "Memory": "536870912",
      247. "Region": "cn-hangzhou-dg-a01",
      248. "OsDiskType": "cloud_efficiency",
      249. "TmpDataDiskSize": "0",
      250. "ProviderId": "26842",
      251. "EndTime": "2019-01-01T21:00:00Z",
      252. "SsdDataDiskSize": "0",
      253. "StartTime": "2019-01-01T20:00:00Z",
      254. "CloudDataDiskSize": "0",
      255. "Bandwidth": "1048576"
      256. },
      257. {
      258. "CPUs": "1",
      259. "InstanceId": "i-bp1hmito9v058zbua0eb",
      260. "OsDiskSize": "42949672960",
      261. "NetworkIn": "0",
      262. "NetworkOut": "0",
      263. "ImageOS": "Windows Server 2016",
      264. "Disk": "0",
      265. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      266. "DataDiskSize": "0",
      267. "IoOptimized": "true",
      268. "ImageSize": "42949672960",
      269. "Memory": "536870912",
      270. "Region": "cn-hangzhou-dg-a01",
      271. "OsDiskType": "cloud_efficiency",
      272. "TmpDataDiskSize": "0",
      273. "ProviderId": "26842",
      274. "EndTime": "2019-01-01T22:00:00Z",
      275. "SsdDataDiskSize": "0",
      276. "StartTime": "2019-01-01T21:00:00Z",
      277. "CloudDataDiskSize": "0",
      278. "Bandwidth": "1048576"
      279. },
      280. {
      281. "CPUs": "1",
      282. "InstanceId": "i-bp1hmito9v058zbua0eb",
      283. "OsDiskSize": "42949672960",
      284. "NetworkIn": "0",
      285. "NetworkOut": "0",
      286. "ImageOS": "Windows Server 2016",
      287. "Disk": "0",
      288. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      289. "DataDiskSize": "0",
      290. "IoOptimized": "true",
      291. "ImageSize": "42949672960",
      292. "Memory": "536870912",
      293. "Region": "cn-hangzhou-dg-a01",
      294. "OsDiskType": "cloud_efficiency",
      295. "TmpDataDiskSize": "0",
      296. "ProviderId": "26842",
      297. "EndTime": "2019-01-01T23:00:00Z",
      298. "SsdDataDiskSize": "0",
      299. "StartTime": "2019-01-01T22:00:00Z",
      300. "CloudDataDiskSize": "0",
      301. "Bandwidth": "1048576"
      302. },
      303. {
      304. "CPUs": "1",
      305. "InstanceId": "i-bp1hmito9v058zbua0eb",
      306. "OsDiskSize": "42949672960",
      307. "NetworkIn": "0",
      308. "NetworkOut": "0",
      309. "ImageOS": "Windows Server 2016",
      310. "Disk": "0",
      311. "InstanceTypeName": "ecs.t5-lc2m1.nano",
      312. "DataDiskSize": "0",
      313. "IoOptimized": "true",
      314. "ImageSize": "42949672960",
      315. "Memory": "536870912",
      316. "Region": "cn-hangzhou-dg-a01",
      317. "OsDiskType": "cloud_efficiency",
      318. "TmpDataDiskSize": "0",
      319. "ProviderId": "26842",
      320. "EndTime": "2019-01-02T00:00:00Z",
      321. "SsdDataDiskSize": "0",
      322. "StartTime": "2019-01-01T23:00:00Z",
      323. "CloudDataDiskSize": "0",
      324. "Bandwidth": "1048576"
      325. }
      326. ],
      327. "HostId": "cn",
      328. "Marker": "079:00000000000000002895:r:01_1711245632317223:02_vm:03_26842:04_i-bp1hmito9v058zbua0eb:05_000000000001546696800:"
      329. },
      330. "Message": "Successful!",
      331. "RequestId": "9A1F6898-EFAA-4C16-A38E-AA3A98FBF232",
      332. "Success": true,
      333. "Code": "Success"
      334. }

      ]]> 导出镜像_自定义镜像_镜像_云服务器 ECS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 导出镜像

      更新时间:2020-06-01 16:23:44

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      创建自定义镜像后,您可以导出镜像到OSS存储空间(OSS Bucket),并下载到本地使用。本文介绍导出自定义镜像的操作步骤及相关注意事项。

      前提条件

      • 自定义镜像所在地域中已有可用的OSS Bucket。

        若尚未创建OSS Bucket,请先创建存储空间

        说明 导出自定义镜像会产生一定的OSS存储和下载的流量费用。计费详情,请参见计量项和计费项

      • 待导出的自定义镜像满足以下限制条件:
        • 不是基于镜像市场镜像创建的自定义镜像。
        • 不能包含Windows Server系列操作系统。
        • 不能包含四块以上数据盘快照,单块数据盘容量最大不能超过500GiB。

      背景信息

      导出自定义镜像的注意事项如下:

      • 导出镜像所需时间取决于自定义镜像文件的大小和当前导出任务的并发数,需要您耐心等待。
      • 导出的自定义镜像包含数据盘快照时,您的OSS Bucket中会出现多个文件。

        文件名带有system的表示系统盘快照,文件名带有data的表示数据盘快照。数据盘快照会有与数据盘对应的标识,即数据盘的挂载点,如xvdb或者xvdc。

      • 使用导出的全镜像创建相同配置的Linux系统时,您需要确认/etc/fstab中记录的文件设备是否与导出的数据盘快照信息互相对应。
      • 如果创建自定义镜像时云盘内无数据,那么导出镜像后解压缩也无数据。

      操作步骤

      1. 登录ECS管理控制台
      2. 在左侧导航栏,单击实例与镜像 > 镜像
      3. 在顶部菜单栏左上角处,选择地域。
      4. 授权ECS服务访问OSS的权限。
        1. 在自定义镜像的操作列中,单击更多 > 导出镜像
        2. 导出镜像对话框里,单击提示信息里的确认地址

          导出镜像

        3. 云资源访问授权对话框,单击同意授权授权ECS服务访问您的OSS资源。
      5. 在左侧导航栏,单击实例与镜像 > 镜像
      6. 自定义镜像列表页签,选择目标镜像,在操作列中,再次单击更多 > 导出镜像
      7. 导出镜像对话框中,配置如下参数:

        • 选择镜像导出的格式,支持RAW、VHD、QCOW2、VDI和VMDK。

          说明 选择镜像导出格式功能正在逐步开放中。

        • 选择一个与自定义镜像所属地域相同的OSS Bucket。
        • 为自定义镜像的Object名称设置一个前辍。例如,您可以将Demo设为前辍,则导出的自定义镜像文件,在OSS Bucket中的名称即为Demo-[系统自动生成的文件名]

      8. 单击确定,开始导出自定义镜像。

        在任务完成前,您都可以通过任务管理,在相应地域下找到导出自定义镜像的任务,取消导出自定义镜像。

      后续步骤

      下载自定义镜像。具体操作步骤,请参见下载自定义镜像文件

      说明 如果您选择RAW作为镜像格式,导出后镜像文件扩展名默认为.raw.tar.gz,解压后扩展名为.raw。如果您的本地计算机为Mac OS X系统,推荐您使用gnu-tar解压工具。

      ]]> 独享云虚拟主机、共享云虚拟主机、云服务器 ECS 的区别?_产品概述_产品简介_云虚拟主机-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 独享云虚拟主机、共享云虚拟主机、云服务器 ECS 的区别?

      更新时间:2018-03-16 10:57:13

      本页目录

      产品特点

      共享云虚拟主机:共享虚拟主机即一台服务器被划分成多个一定大小的空间,每个空间都给予单独的 FTP 权限和 Web 访问权限,多个用户共同平均使用这台服务器的硬件资源。

      独享云虚拟主机:与共享云虚拟主机相比,最大的不同是资源独享。享有整个服务器的软硬件资源,即每台轻云服务器的 CPU、内存、带宽、硬盘均为独享,且不限流量,具有独立 IP,预装了网站应用环境就和数据库环境,同时具备共享云虚拟主机和云服务器的优势。提供可视化操作的控制面板环境,操作简单,即买即用。

      云服务器:是一种弹性计算服务,支持各种应用软件灵活扩展,需要有专业技术人员来维护。

      注意: 虚拟主机不支持远程登录,包括SSH方式,远程桌面RDP方式等。如果您需要远程桌面权限管理,建议您购买云服务器 ECS 。

      适用用户

      共享云虚拟主机:资源共享,空间较大,固定流量,经济实惠,满足基本建站。

      独享云虚拟主机:独享资源,空间超大,不限流量,更高配置,企业建站首选。

      云服务器 ECS  :有技术实力、懂得服务器配置及维护的用户及开发者。

      主要配置

      主要配置

      虚拟主机

      独享版虚拟主机

      云服务器ECS

      网页空间

      M/G级空间

      G级空间

      独享整块硬盘

      CPU

      共享

      独享

      独享

      内存

      共享

      独享

      独享

      带宽

      共享

      独享

      独享

      流量

      有限制

      无限制

      无限制

      主机管理控制台

      支持

      支持

      不支持

      付费方式

      年付

      月付/年付

      月付/年付

      下一篇:什么是临时域名

      相关文档

      相关产品

      • 云虚拟主机

        阿里云虚拟主机主要用于搭建网站,提供预装网站运行环境,赠送正版数据库,可通过图形化控制面板管理,包括独享系列虚机和共享系列虚机。独享系列适合企业建站客户,提供独享的服务器资源,无资源争抢更稳定,不限流量更快速、独立IP更易推广;共享系列适合于开发者、个人站长建站,多客户共享服务器硬件资源,价格优惠,简单易用

      • 云服务器 ECS

        云服务器(Elastic Compute Service,简称 ECS)是一种简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本,使您更专注于核心业务创新。

      • 云解析 DNS

        云解析DNS(Alibaba Cloud DNS)是一种安全、快速、稳定、可扩展的权威DNS服务,云解析DNS为企业和开发者将易于管理识别的域名转换为计算机用于互连通信的数字IP地址,从而将用户的访问路由到相应的网站或应用服务器。

      以上内容是否对您有帮助?

      在文档使用中是否遇到以下问题

      • 内容错误

      • 更新不及时

      • 链接错误

      • 缺少代码/图片示例

      • 太简单/步骤待完善

      • 其他

      • 内容错误

      • 更新不及时

      • 链接错误

      • 缺少代码/图片示例

      • 太简单/步骤待完善

      • 其他

      更多建议

      匿名提交

      感谢您的打分,是否有意见建议想告诉我们?

      感谢您的反馈,反馈我们已经收到

      文档反馈

      ]]>
      ALIYUN::ECS::Command_ECS_资源类型_资源编排-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ALIYUN::ECS::Command

      更新时间:2020-02-25 10:06:55

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      ALIYUN::ECS::Command 类型用于新建云助手命令。

      语法

      {
        "Type": "ALIYUN::ECS::Command",
        "Properties": {
          "Name": String,
          "WorkingDir": String,
          "CommandContent": String,
          "Timeout": Integer,
          "Type": String,
          "Description": String
        }
      }

      属性

      属性名称 类型 必须 允许更新 描述 约束
      Name String 命令名称,支持全字符集。长度不得超过 30 个字符。
      WorkingDir String 您创建的命令在 ECS 实例中运行的目录。
      CommandContent String

      命令 Base64 编码后的内容。

      当您传入请求参数 Type 后,必须同时传入该参数。

      该参数的值必须使用 Base64 编码后传输,且脚本内容的大小在 Base64 编码之后不能超过 16 KB。

      Timeout Integer

      您创建的命令在 ECS 实例中执行时最大的超时时间,单位为秒。

      当因为某种原因无法运行您创建的命令时,会出现超时现象;超时后,会强制终止命令进程,即取消命令的 PID。

      默认值:3600

      Type String

      命令的类型。

      取值范围:
      • RunBatScript:创建一个在 Windows 实例中运行的 Bat 脚本
      • RunPowerShellScript:创建一个在 Windows 实例中运行的 PowerShell 脚本
      • RunShellScript:创建一个在 Linux 实例中运行的 Shell 脚本
      Description String 命令描述,支持全字符集。长度不得超过100个字符。

      返回值

      Fn::GetAtt

      • CommandId:命令 ID。

      示例

      {
        "ROSTemplateFormatVersion": "2015-09-01",
        "Parameters": {
          "WorkingDir": {
            "Type": "String",
            "Description": "The path where command will be executed in the instance."
          },
          "CommandContent": {
            "Type": "String",
            "Description": "The content of command. Content requires base64 encoding. Maximum size support 16KB."
          },
          "Type": {
            "Type": "String",
            "Description": "The type of command."
          },
          "Description": {
            "Type": "String",
            "Description": "The description of command."
          },
          "Timeout": {
            "Type": "Number",
            "Description": "Total timeout when the command is executed in the instance. Input the time unit as second. Default is 3600s."
          },
          "Name": {
            "Type": "String",
            "Description": "The name of command."
          }
        },
        "Resources": {
          "Command": {
            "Type": "ALIYUN::ECS::Command",
            "Properties": {
              "WorkingDir": {
                "Ref": "WorkingDir"
              },
              "CommandContent": {
                "Ref": "CommandContent"
              },
              "Type": {
                "Ref": "Type"
              },
              "Description": {
                "Ref": "Description"
              },
              "Timeout": {
                "Ref": "Timeout"
              },
              "Name": {
                "Ref": "Name"
              }
            }
          }
        },
        "Outputs": {
          "CommandId": {
            "Description": "The id of command created.",
            "Value": {
              "Fn::GetAtt": [
                "Command",
                "CommandId"
              ]
            }
          }
        }
      }

      ]]> 更换源站ECS公网IP_接入DDoS高防_DDoS高防(新BGP&国际)_DDoS防护-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 更换源站ECS公网IP

      更新时间:2020-05-29 09:36:26

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      若您的源站IP已暴露,建议您更换阿里云ECS云服务器的公网IP,防止黑客绕过DDoS高防直接攻击源站。您可以在DDoS高防管理控制台更换后端ECS的IP,每个账号最多可更换10次。

      背景信息

      更换ECS IP功能仅支持使用经典网络公网IP的ECS更换IP。

      操作步骤

      1. 登录DDoS高防控制台
      2. 在顶部导航栏,选择服务所在地域:
        • 中国内地:DDoS高防(新BGP)服务
        • 非中国内地:DDoS高防(国际)服务
      3. 在左侧导航栏,单击接入管理 > 域名接入
      4. 域名接入页面右上方,单击更换ECS IP

        注意 更换ECS IP会使您的业务暂时中断几分钟,建议您在操作前先备份好数据。

      5. 更换ECS IP需要将ECS停机,若您已将需要更换IP的ECS停机,请直接跳转到步骤6。在更换ECS IP对话框,单击前往ECS,并在ECS管理控制台将需要更换IP的ECS实例停机。
        1. 在实例列表中找到目标ECS实例,单击其实例ID。
        2. 在实例详情页,单击停止
        3. 选择停止方式,并单击确定

          注意 停止ECS实例是敏感操作,稳妥起见,需要您输入手机校验码。

        4. 等待ECS实例状态变成已停止
      6. 返回更换ECS IP对话框,输入ECS实例ID,并单击下一步
      7. 确认当前ECS实例信息准确无误(尤其是ECS IP)后,单击释放IP
      8. 成功释放原IP后,单击下一步,为该ECS实例自动分配新的IP。
      9. ECS IP更换成功后,单击确认,完成操作。

        说明 更换IP成功后,请将新的IP隐藏在DDoS高防后面,不要对外暴露。

      ]]> ECS实例与MongoDB实例地域不同时如何连接_连接实例_用户指南_云数据库 MongoDB-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ECS实例与MongoDB实例地域不同时如何连接

      更新时间:2020-04-21 14:44:03

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      当ECS实例与MongoDB实例的地域不同时,您可根据本文中的办法快速实现两者之间的连接。

      方法一:将MongoDB实例迁移至ECS实例所属地域

      本方法通过数据传输服务DTS(Data Transmission Service)的数据迁移功能,实现迁移MongoDB实例至ECS实例所属地域的目的,例如将MongoDB实例从华北1迁移至华东1。

      迁移MongoDB至其他地域
      1. 在ECS所属地域创建MongoDB实例,详情请参见创建实例,如果已创建可跳过本步骤。
      2. 将源地域下的MongoDB数据库迁移至目标MongoDB实例,详情请参见迁移MongoDB实例至其他地域
      3. 将ECS实例的IP地址加入目标MongoDB实例的白名单中,详情请参见设置白名单

        说明 关于获取ECS实例IP地址信息,请参见如何查询ECS实例的IP地址

      方法二:将ECS实例迁移至MongoDB实例所属地域

      下述两种方法分别通过自定义镜像和迁云工具,实现迁移ECS实例数据至MongoDB实例所属地域的目的,例如将ECS实例从华北1迁移至华东1。

      迁移ECS地域

      ]]> ECS自建库录入_实例列表_实例管理_数据管理 DMS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ECS自建库录入

      更新时间:2020-06-18 10:29:00

      本页目录

      需求背景

      • 目前云上除了云数据库外,也有用户通过ECS自己搭建的各种数据库。本文主要描述ECS自建库如何快速录入进行管理。

      关键步骤

      基本录入步骤需要提供信息可参见:实例管理

      1. 在填写完基本信息点击【测试连接】,会遇到如下错误。

        测试连接

      2. 此时需要前往ECS控制台进行白名单的添加,注意“授权对象”为报错弹层上的DMS服务器白名单地址(含网段)。

        添加

      3. 按照步骤2完成入口方向、出口方向,双向配置【注:仅单向配置会无法使用】。

        • 入口方向入口

        • 出口方向出口

      4. 完成出口、入口配置后,再测试连接即可访问进入保存环节【注:ECS白名单策略添加后可能存在轻微的延迟,建议稍后1-3分钟再到DMS企业版内重试连接】。

      ]]> 对ECS集群中的应用进行弹性伸缩_应用管理_ECS集群用户指南_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 对ECS集群中的应用进行弹性伸缩

      更新时间:2020-04-08 14:19:41

      本页目录

      在分布式应用管理中,弹性伸缩是很重要的一个运维能力。弹性伸缩能够感知应用内各个实例的状态,并根据状态动态实现应用扩容、缩容。在保证服务质量的同时,提升应用的可用率。

      弹性伸缩简介

      互联网、游戏类等应用在促销活动期间容易出现突发性流量洪流,SLA 和资源成本不易平衡,极易造成系统响应延迟、系统瘫痪等问题。EDAS 继承阿里巴巴应对双 11 的流量洪流技术,提供秒级自动弹性功能,保证 SLA 的同时也节省机器保有成本。多适用于互联网、游戏以及社交平台等行业。

      弹性伸缩适用于在ECS集群中创建的应用。在单实例环境中,弹性伸缩可确保始终有一个正在运行的实例。在流量变化很快的环境中,通过弹性伸缩配置要运行的实例数范围,EDAS 将根据设置的负载规则按需添加或删除实例。

      弹性伸缩功能根据应用实例的以下指标来判断并实现自动扩容或者缩容:

      • CPU:CPU 使用率,以百分比表示。
      • RT:对请求作出的响应时间,单位为 ms 。
      • Load:应用实例的负载大小,以正整数表示。

      自动弹性伸缩

      弹性伸缩包括自动扩容和自动缩容,可以分别配置扩容规则或者缩容规则。下面以配置扩容规则为例来展示操作步骤。

      说明

      • 同时配置扩容和缩容规则的时候,缩容规则的指标不能大于扩容规则的指标,否则,在单击保存的时候会弹出错误提示。
      • 若使用了弹性资源,缩容时会优先释放弹性资源提供的实例。

      1. 登录 EDAS 控制台,在页面左上角选择所需地域。
      2. 在左侧导航栏中选择应用管理 > 应用列表 ,在应用列表页面单击具体的应用名称。
      3. 在应用详情页面左侧的导航栏中,单击弹性伸缩
      4. 扩容规则区域右上角打开开关启用扩容规则。
      5. 配置扩容规则参数,然后在弹性伸缩页面右下角单击保存
        1. 配置触发指标:设置 CPU、RT 和 Load 指标的阈值。当超过阈值时,触发扩容。
        2. 选择触发条件

          • 任一指标:表示设定的指标中任意一个指标被触发都会引起扩容。
          • 所有指标:表示设定所有指标必须全部被触发才能引发自动扩容操作。

        3. 持续时间超过:指标持续被触发的时间,单位为分钟。表示在持续时间内,指标每分钟的平均值持续达到设置的阈值,就会引起扩容操作,您可根据集群服务能能力的灵敏度酌情配置。
        4. 配置应用来源

          • 已有资源:自动扩容时会从当前应用所在集群选择指定数量的闲置 ECS 实例扩容到该应用。

            说明 当所在集群中现有的 ECS 实例数量不够,无法满足扩容需求时,EDAS 会根据已有实例数量来进行扩容。

          • 弹性资源:基于现有实例规格或实例启动模版来代购实例,然后自动将代购的实例导入所在集群并用于应用扩容。ECS集群中代购实例用于弹性伸缩
            参数 描述
            创建方式
            • 基于现有实例规格购买:从所在集群内已有的实例规格中选择一个作为模板来代购实例。
            • 基于实例启动模版购买:会基于您在 ECS 控制台创建的实例启动模版来代购实例。
            模板主机/启动模板 选择一个现有实例规格或者选择一个启动模板作为模板来代购实例。
            登录密钥 当选择基于现有实例规格购买时,需要选择登录密钥。
            服务协议 勾选《云服务器 ECS 服务条款》 | 《镜像商品使用条款》
            高级选项(可选)
            • 基于现有实例规格购买:输入登录密码后,新创建的 ECS 实例将以此密钥对作为 SSH 登录认证方式。
            • 基于实例启动模版购买网络类型为您需要扩容的当前应用所在的网络,不可更改。如果当前网络为 VPC 网络,需要指定新创建实例连接的虚拟交换机;若指定多个虚拟交换机,EDAS 将通过多可用区扩缩容策略来进行自动分配。
          • 已有资源优先:自动扩容时优先使用集群内空闲实例,如果集群内的空闲实例不足,则使用弹性伸缩功能为您代购实例。

        5. 设置每次扩容的实例数:每次触发扩容操作后,自动增加的实例个数。
        6. 设置分组最大实例数:当集群内服务器数量达到配置的最大实例数后,不再继续扩容,请根据您的资源限额配置。

      查看弹性伸缩结果

      设置了弹性伸缩规则后,如果发生了自动扩容或者自动缩容操作后,您可以通过以下方式来查看伸缩结果:

      • 在应用的基本信息页面中查看实例数量是否增加或者减少。
      • 在应用详情页面的左侧导航栏单击变更记录。对于变更类型为应用扩容应用缩容,且来源是auto_scale的变更记录,在操作列单击查看进入变更详情页面查看变更明细。弹性伸缩变更记录

      ]]> 在自定义环境中代购的 ECS 实例上部署微服务应用_快速创建 Demo 微服务应用_快速入门_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 在自定义环境中代购的 ECS 实例上部署微服务应用

      更新时间:2019-09-18 19:36:26

      本页目录

      为方便您快速开始使用 EDAS,EDAS 为您准备了基于不同应用框架(Spring Cloud、Dubbo 和 HSF)的微服务应用 Demo,您可以将应用 Demo 快速部署到 EDAS 中。本文介绍如何在自定义环境中(指定 VPC 和集群)中代购 ECS 实例快速部署 Spring Cloud 微服务应用 Demo。

      前提条件

      在默认环境中代购的 ECS 实例中部署应用前,请先完成以下工作:

      操作步骤

      微服务应用 Demo 中都会包含一个服务提供者和服务消费者。下面以服务提供者为例介绍如何创建,在创建完服务提供者之后,请按照操作步骤再创建服务消费者。

      创建服务提供者的步骤如下:

      1. 登录 EDAS 控制台。

      2. 概览页面应用数上方单击创建新应用

        创建新应用

      3. 创建应用页面的应用基本信息页签中选择集群类型应用运行环境,输入应用名称应用描述(可选),然后单击下一步

        应用基本信息-Spring Cloud

        • 集群类型:选择 ECS 集群
        • 应用运行环境:针对不同应用框架,EDAS 提供了不同的应用运行环境,您可以根据您的应用框架选择,本示例中选择 Java

          Java 环境:选择 Open JDK 8

        • 应用名称:输入应用名称,如 app-demo-springCloud-provider
      4. 应用配置页签中选择部署包来源Demo 类型实例,然后单击下一步

        应用配置-SC-自定义环境

        • 部署包来源:选择官方 Demo

        • Demo 类型:在下拉列表中选择具体的应用 Demo,如 Spring Cloud 服务端应用

        • 实例:选择自定义

          选择自定义后,界面会根据您的账号当前的资源情况有所不同。

          • 网络和环境

            • 如果您当前没有 VPC、命名空间和集群,EDAS 会为您创建默认环境。

            • 如果您已经创建过 VPC、命名空间和集群等资源,会显示对应资源的选择列表。您可以在下拉列表中选择对应资源。

          • 实例

            • 选择实例规格:选择实例规格,如超小规格实例
            • 购买数量:选择要购买的实例数量,如 1
            • 服务协议:勾选《云服务器 ECS 服务条款》 | 《镜像商品使用条款》
      5. 应用高级配置页签中输入版本应用健康检查(可选),然后单击创建应用

        • 版本:EDAS 配置使用当前时间戳作为版本,格式如 yyyymmdd:hhmmss。您也可以设置其它版本标识。
        • 应用健康检查:设置健康检查的 URL,用来检测应用是否健康运行。
      6. 应用创建完成页签确认应用基本信息、应用配置和应用高级设置,确认无误后,单击确定创建应用

        应用创建完成

      结果验证

      在服务端应用和客户端应用创建后,EDAS 将为该应用自动创建一条变更记录,您可以通过变更详情查看该应用创建的进度和状态。应用创建成功后,变更记录显示执行成功

      1. 应用创建成功后,返回该应用的基本信息页面,查看应用的相关信息,应该和创建时保持一致。

        结果验证-基本信息

      2. 单击实例部署信息查看该应用的状态,运行状态应为运行正常,变更状态(即第一次创建、部署)应为成功。同时,显示代购的 ECS 实例的 ID、IP、规格及 VPC 信息。

        结果验证-实例部署信息

      1. 验证应用 Demo 的调用是否成功。

        说明:验证调用需要按结果验证的步骤 1 和 2 确保服务端应用和客户端应用都部署成功。

        1. 进入客户端应用的详情页,在左侧导航栏单击基本信息,然后在实例部署信息页面单击 ECS 的 IP 地址。

        2. SC 客户端 页面的 Echo this String 文本框中输入任意字符串,如 test,然后单击 点击此处

          结果验证-调用

        3. SC 客户端 页面下方可以看到调用结果,返回了 test 字符串,说明调用成功。

          结果验证-调用成功

      ]]> 阿里云NTP服务器_同步服务器本地时间_管理实例_实例_云服务器 ECS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 阿里云NTP服务器

      更新时间:2020-04-22 08:38:31

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      阿里云提供了内网和公网NTP服务器,用于同步各网络中ECS实例的本地时间。

      内网和公网NTP服务器

      NTP是用于同步网络中计算机时间的协议,全称为网络时间协议(Network Time Protocol)。

      时区和时间一致性对于云服务器ECS非常重要,有时会直接影响到任务执行的结果。例如,您在更新数据库或者分析日志时,时间顺序对结果有很大影响。为避免在ECS实例上运行业务时出现逻辑混乱和网络请求错误等问题,您需要统一相关ECS实例的时区设置。另外,您还可以通过NTP服务同步各网络中ECS实例的本地时间。

      云服务器ECS为您提供了高精度的时间参考NTP服务器,其中ntp.cloud.aliyuncs.com服务器提供分布式的一级时钟源,适用于金融、通讯、科研和天文等以时间精度核心的生产行业。阿里云NTP服务也用于同步ECS实例和其它云产品的本地时间。各网络下的阿里云NTP服务器地址如下表所示。

      经典网络内网 专有网络VPC内网 公网
      - ntp.cloud.aliyuncs.com ntp.aliyun.com
      ntp1.cloud.aliyuncs.com ntp7.cloud.aliyuncs.com ntp1.aliyun.com
      ntp2.cloud.aliyuncs.com ntp8.cloud.aliyuncs.com ntp2.aliyun.com
      ntp3.cloud.aliyuncs.com ntp9.cloud.aliyuncs.com ntp3.aliyun.com
      ntp4.cloud.aliyuncs.com ntp10.cloud.aliyuncs.com ntp4.aliyun.com
      ntp5.cloud.aliyuncs.com ntp11.cloud.aliyuncs.com ntp5.aliyun.com
      ntp6.cloud.aliyuncs.com ntp12.cloud.aliyuncs.com ntp6.aliyun.com
      - - ntp7.aliyun.com

      其他互联网基础服务

      阿里云还提供了其他的互联网基础服务,如下表所示。

      公共服务 描述
      公共DNS:223.5.5.5 / 223.6.6.6 域名:http://www.alidns.com
      公共镜像站:https://developer.aliyun.com/mirror 镜像同步频率:每天凌晨2:00−4:00。覆盖了大多数开源软件及Linux发行版。

      相关操作

      ]]> ECS迁移到时空_数据迁移_用户指南_时空数据库_时序时空数据库 TSDB-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 ECS迁移到时空

      更新时间:2019-05-06 19:09:38

      本页目录

      前提条件

      • ECS上自建基于PostgreSQL的PostGIS数据库或Timescale数据库
      • 已完成时空数据库的准备,包括数据库,账号,密码创建等。参见: 账号管理

      准备数据

      1. 登录云服务器ECS
      2. 执行pg_dump命令,备份数据

        1. pg_dump -U username -h hostname -p port databasename -f filename
        2. # 例如:使用postgres用户,备份gaia数据库,备份文件是gaia.sql
        3. pg_dump -U postgres -h localhost -p 5432 gaia -f gaia.sql

        参数说明如下:

        • username:ECS数据库用户名
        • hostname:ECS数据库主机名,如果是在本地ECS数据库主机登录,可以使用localhost
        • port:ECS数据库端口号
        • databasename:要备份的数据库名
        • filename:要生成的备份文件名称

      迁移到时空数据库

      1. 通过PostgreSQL客户端,执行如下命令将数据导入到时空数据库中

        1. psql -U username -h ip -d databasename -p port -f filename
        2. # 一个例子:其中47.110.145.173:3242是时空数据库地址
        3. psql -U postgres -h 47.110.145.173 -d postgres -p 3242 -f gaia.sql

        参数说明如下:

        • username:时空数据库用户名
        • ip:时空数据库地址
        • port:时空数据库端口号
        • databasename:时空数据库名
        • filename:ECS上备份的数据文件名

      ]]> 限制不同交换机下的ECS间的互通_最佳实践_网络ACL_专有网络 VPC-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 限制不同交换机下的ECS间的互通

      更新时间:2020-04-23 17:30:06

      本页目录

      本文为您介绍如何通过网络ACL功能限制不同交换机下的ECS的互通关系。

      前提条件

      操作前,请确保满足以下条件。
      • 已经注册了阿里云账号。如还未注册,请先完成账号注册。详细信息,请参见账号注册
      • 已经创建了VPC和交换机。详细信息,请参见创建专有网络
      • 已经在交换机中创建了ECS实例。详细信息,请参见使用向导创建实例

      背景信息

      某公司在云上创建了VPC,在VPC中创建了两个交换机,交换机1下创建了ECS1实例(192.168.1.206),交换机2下创建了ECS2实例(192.168.0.229)和ECS3实例(192.168.0.230)。因公司业务需要,要求ECS实例间、ECS与互联网间必须满足以下互通关系。

      • 禁止ECS1实例、ECS2实例、ECS3实例与互联网互通。
      • 禁止ECS1与ECS3互通。
      • 允许ECS1与ECS2互通。

      网络ACL场景

      如上图,您可以通过自定义设置网络ACL规则,并将网络ACL与交换机绑定,实现对交换机中ECS的流量的访问控制。

      配置流程图如下:网络ACL使用流程

      步骤一:创建网络ACL

      完成以下操作,创建网络ACL。

      1. 登录专有网络管理控制台
      2. 在左侧导航栏,单击网络ACL
      3. 在顶部状态栏处,选择网络ACL的地域。

        说明 目前,仅华北1(青岛)、华北2(北京)、华北5(呼和浩特)、西南1(成都)、华东1(杭州)、华东2(上海)、华南1(深圳)、华南2(河源)、中国香港、英国(伦敦)、美国(硅谷)、新加坡、德国(法兰克福)、印度(孟买)地域支持网络ACL功能。

      4. 网络ACL页面,单击创建网络ACL
      5. 创建网络ACL对话框中,根据以下信息配置网络ACL,然后单击确定

        • 专有网络:选择网络ACL所属的专有网络。
        • 名称:输入网络ACL的名称。
        • 描述:输入网络ACL的描述。

      步骤二:绑定交换机

      完成以下操作,将交换机1、交换机2绑定到网络ACL。

      1. 登录专有网络管理控制台
      2. 在左侧导航栏,单击网络ACL
      3. 在顶部状态栏处,选择网络ACL的地域。
      4. 网络ACL页面,找到目标网络ACL,单击操作列下的管理
      5. 已绑定资源页签下,单击关联资源
      6. 关联资源对话框,选择交换机1和交换机2,然后单击确定

      步骤三:添加网络ACL规则

      完成以下操作,为网络ACL添加入方向规则和出方向规则。

      1. 登录专有网络管理控制台
      2. 在左侧导航栏,单击网络ACL
      3. 在顶部状态栏处,选择网络ACL的地域。
      4. 网络ACL页面,找到目标网络ACL,单击操作列下的设置入方向规则
      5. 设置入方向规则页签下,单击创建入方向规则
      6. 创建入方向规则对话框,根据以下信息配置入方向规则,然后单击确定

        名称 生效顺序 策略 协议类型 源地址 目的端口范围
        允许来自ECS2的流量 1 允许 all 192.168.0.229/32 -1/-1
        允许来自ECS1的流量 2 允许 all 192.168.1.206/32 -1/-1
        拒绝来自所有地址的流量 3 拒绝 all 0.0.0.0/0 -1/-1

      7. 单击出方向规则页签,然后单击创建出方向规则
      8. 创建出方向规则对话框,根据以下信息配置出方向规则,然后单击确定

        名称 生效顺序 策略 协议类型 目的地址 目的端口范围
        允许去往ECS2的流量 1 允许 all 192.168.0.229/32 -1/-1
        允许去往ECS1的流量 2 允许 all 192.168.1.206/32 -1/-1
        拒绝去往所有地址的流量 3 拒绝 all 0.0.0.0/0 -1/-1

      步骤四:测试连通性

      完成以下操作,测试ECS间、ECS与互联网间的连通性。

      1. 登录ECS1实例。
      2. 通过ping命令分别pingECS2实例、ECS3实例、任意公网IP地址,验证通信是否正常。

        经验证,ECS1实例能访问ECS2实例,但不能访问ECS3实例和互联网。

        图 1. ECS1实例能访问ECS2实例
        ECS1实例能访问ECS2实例
        图 2. ECS1实例不能访问ECS3实例
        ECS1实例不能访问ECS3实例
        图 3. ECS1实例不能访问互联网
        ECS1实例不能访问互联网

      ]]> 创建 ECS 集群_集群_资源管理_ECS集群用户指南_企业级分布式应用服务 EDAS-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 创建 ECS 集群

      更新时间:2019-11-04 20:15:12

      本页目录

      在 EDAS 中创建一个 ECS 集群后,可以进入集群中直接购买 ECS 实例或导入已购买的 ECS 实例。 ##创建 ECS 集群

      创建 ECS 集群

      1. 登录 EDAS 控制台,在页面左上角选择地域。
      2. 在左侧导航栏中,选择资源管理 > 集群
      3. 集群页面上方选择命名空间,然后在右侧单击创建集群
      4. 创建集群对话框中设置集群参数,然后单击创建

        EDAS资源管理之创建ECS集群

        • 集群名称:输入集群名称。名字仅支持字母、数字、下划线(_)、中划线(-)和点(.),且长度不能超过64个字符。
        • 集群类型:选择 ECS
        • 集群网络类型:包括经典网络和 VPC 网络。根据实际需求在下拉菜单中选择网络类型。

        • VPC 网络:在下拉菜单中选择 VPC。

          说明:如果没有可选 VPC 网络,请单击创建 VPC 跳转到 VPC 控制台创建。

        • 命名空间:显示在集群列表页面选择的命名空间,不可配置。如果未选择,则默认显示地域。

        集群创建成功后当前页面右上角会出现创建成功的提示,同时新创建的集群会在集群列表中显示。

      添加 ECS 实例

      在 ECS 集群中添加 ECS 实例有两种方式:

      购买 ECS 实例

      1. 在集群列表选择 EDAS 集群页签,单击创建的 ECS 集群名称进入集群详情页面。
      2. 集群详情页面中右侧单击购买 ECS 扩容
      3. 集群扩容页面选择扩容方式,设置完成后单击下一步

        • 基于现有实例规格购买EDAS资源管理之创建ECS集群购买 ECS 实例
          1. 勾选集群中现有的实例作为规格模板,然后单击下一步
          2. 购买信息页签,选择计费方式购买数量,然后单击下一步
          3. 确认扩容页签,确认扩容信息,然后单击确认扩容
        • 基于启动实例模板购买: EDAS资源管理之创建ECS集群基于启动实例模板购买
          1. 扩容方式页签,选择启动模板和版本,然后单击下一步
          2. 购买信息页签,选择计费方式购买数量,然后单击下一步
          3. 确认扩容页签,确认扩容信息,然后单击确认扩容

      4. 执行完扩容操作后,会在页面上方提示已触发自动购买的流程,请查看变更流程获取实时信息。当实例导入完成后,返回集群详情页,实例的健康检查显示为运行中则表示实例购买成功。

      导入 ECS 实例

      EDAS 集群中导入 ECS 实例目前有两种操作分支:

      • 直接导入:无需镜像转化。
      • 转化后导入:使用 EDAS 官方镜像重装系统。重装后,实例中的所有数据都会被删除,并且需要设置新的实例登录密码。 ECS 实例满足以下任一情况,则不能直接导入:

        • 2017年12月1日之前创建的实例
        • 向经典网络的集群中导入的经典网络实例
        • 实例没有运行(已停止、启动中或停止中)
        • Windows 系统实例或不支持简单 shell 命令的实例
        • 非 ECS 集群间导入的实例
      1. 在集群列表选择 EDAS 集群页签,单击创建的 ECS 集群名称进入集群详情页面。
      2. 集群详情页面中右侧单击购买 ECS 扩容右侧的下拉按钮,在下拉列表中单击添加已有 ECS
      3. 添加ECS实例页面的实例列表中,选择导入方式和 ECS 实例,然后单击下一步
        • 导入 ECS:命名空间和导入集群不可配置,从该地域下默认的命名空间向集群中导入。
        • 从已有集群选择:在该地域下,选择命名空间以及源集群。然后将页面左侧列表中的 ECS 实例添加到右侧列表中。EDAS资源原理导入ECS之从已有集群选择

          说明 如果没有符合条件的实例,在页面右上角单击创建 ECS 实例,跳转到阿里云官网 ECS 购买页面,购买并创建新的 ECS 实例。详情请参考创建 ECS 实例

      4. 准备导入页面,查看实例的导入状态,确认实例信息,单击确认并导入
        • 直接导入:在准备导入页面,查看选择的实例信息,单击确认并导入
        • 转化后导入:在准备导入页面,查看选择的实例信息,勾选同意对以上需要导入的实例进行转化,并已知转化后原有系统中的数据将会丢失。并输入转化后系统 root 用户登录的新密码,然后单击确认并导入
      5. 进行导入页面上查看实例的导入状态。当实例导入完成后,返回集群详情页,实例的健康检查显示为运行中则表示导入成功。
        • 直接导入:会显示直接导入成功
        • 转化后导入:开始导入时,会显示为正在转化中,转化操作预计耗时五分钟

      移除 ECS 实例

      1. 在页面 ECS 实例和应用区域实例列表的操作列中,单击移除
      2. 移除 ECS 实例对话框中确认要移除的实例信息,单击移除

        在移除过程中,实例的健康检查状态会显示删除中并显示移除进度的百分比。当删除成功后,该实例将会从实例列表中移除。

      ]]> OOS操作ECS分组资源的权限策略管理_权限配置_最佳实践_运维编排服务-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 OOS操作ECS分组资源的权限策略管理

      更新时间:2019-11-15 12:18:25

      本页目录

      背景

      阿里云账号分为主账号(用户)和子账号(用户)两类,主账号为注册阿里云时的账号,其权限为root,出于安全考虑,建议实际操作时采用子账号。而操作员工的职责不同,不同子账号需要的权限也不同。此时,子账号权限策略的有效管理就很重要。
      本文将介绍下,当某员工通过子账号,使用运维编排服务(OOS)执行某模版,来完成一些例行运维任务时,如何既使子账号顺利完成相应操作,又最大程度保障账号的安全。

      场景介绍

      某公司账号下购买了若干台ECS实例,其中2台被打上标签TagKey:TagValue进行了分组,某员工甲被分配的子账号为subUser1ForOOS,甲定期通过该账号在OOS中执行一个模版T,模版执行的任务是对标签TagKey:TagValue分组下的ECS实例批量执行shell指令。即该员工具有对模版T的只读和执行权限,以及有模版任务涉及API(如RunCommand)的操作权限,且可操作的API仅对标签TagKey:TagValue分组下的实例有效。

      解决方案

      为满足上述场景,权限管理要分两方面,即OOS资源操作和ECS资源操作。
      OOS方面权限策略包括对模版的只读权限、对模版T的执行权限、对执行的查询权限。
      ECS方面权限策略为对标签TagKey:TagValue下实例的操作权限,且操作权限仅限于模版中涉及的API。
      以上的权限策略成功创建后,将其授权给员工甲使用的RAM子用户即可(或具有员工甲相同职责的用户组)。

      操作步骤

      步骤如下

      • ECS实例的分组
      • 创建OOS模版
      • 创建子账号(用户)
      • 创建自定义权限策略
      • 为子账号授权
      • 子账号执行OOS模版
      • 权限策略的补充验证

      ECS实例的分组

      1. 使用阿里云主账号(或管理员账号)登录到ECS控制台oos
      2. 选择2台实例,在操作菜单下选择更多 > 实例设置 > 编辑标签
      3. 单击新建标签,输入标签键标签值,单击确定
        本文中标签键设为TagKey,标签值设为TagValue。oos
      4. 单击确定
      5. 打开实例列表栏,在搜索框中输入TagKey:TagValue,单击放大镜图标。 oos
      6. 确认搜索结果为刚刚打标签TagKey:TagValue的实例。 oos

      创建OOS模版

      1. 使用阿里云主账号(或管理员账号)登录到运维编排控制台
      2. 单击我的模版,单击创建模版,选取空白模版,单击YAML
        本文中模版格式选择YAML。 oos
      3. 将附件中YAML的模版内容复制到空白模版中(若您对实例分组的标签键值不是TagKey:TagValue,请根据实际情况对模版中TagKey和TagValue进行修改)。

        oos

      4. 模版任务如下,请确认模版将执行的任务。

        1. 验证指定标签下是否存在实例。
        2. 根据标签下是否存在停止的实例选择后续任务。
        3. 如果标签下存在停止的实例,则启动这些实例。
        4. 为标签下所有实例安装云助手。
        5. 在标签下所有实例中运行Shell命令。
      5. 填写模板名称,单击创建模版
        本文中,模版名称设为TestOperatingByGroups。oos

      6. 我的模版栏,可查看成功创建的模版TestOperatingByGroups。

      创建子账号(用户)

      1. 使用阿里云主账号(或管理员账号)登录RAM控制台
      2. 在左侧导航栏的人员管理菜单下,单击用户 > 新建用户
      3. 输入登录名称显示名称
        本文中的登录名称及显示名称均设为subUser1ForOOS。
      4. 勾选控制台密码登录,单击自定义登录密码并填写要设置的子账号密码。 oos
      5. 单击确定
      6. 用户栏中可查看成功创建的新账号。oos

      创建自定义权限策略

      1. 使用阿里云主账号登录RAM控制台
      2. 在左侧导航栏的权限管理菜单下,单击权限策略管理
      3. 单击新建权限策略
        创建第一条策略用来管理OOS资源操作。
      4. 填写策略名称备注
        本文中策略名称设置为OOSResourceManage。
      5. 配置模式选择脚本配置,并将下述的第一段JSON策略样例拷贝到策略内容区域,并根据实际情况进行修改(将JSON中所有以$为前缀的变量名替换掉)。
        将第一段JSON中的$AliyunMasterAccountID、$RegionID、$TemplateName1修改为您所使用的阿里云主账号ID、OOS及ECS资源所在地域ID(本文中为cn-hangzhou)、允许子账号可执行的模版名称(本文中为TestOperatingByGroups。))。
        oos
        操作OOS的策略
        该段策略表示:被授权RAM用户具有对$RegionID地域下模版的只读权限、对模版$TemplateName1的执行权限、对$RegionID地域下OOS执行的查询权限。

        1. {
        2. "Statement": [
        3. {
        4. "Action": [
        5. "oos:StartExecution",
        6. "oos:List*",
        7. "oos:Get*"
        8. ],
        9. "Resource": [
        10. "acs:oos:$RegionID:$AliyunMasterAccountID:template/$TemplateName1",
        11. "acs:oos:$RegionID:$AliyunMasterAccountID:execution/*"
        12. ],
        13. "Effect": "Allow"
        14. },
        15. {
        16. "Action": [
        17. "oos:List*",
        18. "oos:Get*"
        19. ],
        20. "Resource": [
        21. "acs:oos:$RegionID:$AliyunMasterAccountID:template/*"
        22. ],
        23. "Effect": "Allow"
        24. }
        25. ],
        26. "Version": "1"
        27. }
      6. 单击确定

      7. 单击新建权限策略,创建第二条策略用来管理ECS资源操作。
      8. 填写策略名称备注
      9. 配置模式选择脚本配置,将下述的第二段JSON策略样例拷贝到策略内容区域,并根据实际情况进行修改(将JSON中所有以$为前缀的变量名替换掉)。
        将第二段JSON中的$AliyunMasterAccountID、$TagKey、$TagValue、$RegionID修改为您所使用的阿里云主账号ID、实例所属标签键和标签值(本文中根据ECS实例的分组,键及值应设为TagKey和TagValue)、OOS及ECS资源所在地域ID。(本文中为cn-hangzhou)。
        ecs
        操作ECS的策略
        该段策略表示:被授权RAM用户具有对标签$TagKey:$TagValue下实例的操作权限,且仅可操作模版中涉及的API。

        1. {
        2. "Statement": [
        3. {
        4. "Effect": "Allow",
        5. "Action": [
        6. "ecs:DescribeInstances",
        7. "ecs:RebootInstance"
        8. ],
        9. "Resource": "acs:ecs:$RegionID:$AliyunMasterAccountID:instance/*",
        10. "Condition": {
        11. "StringEquals": {
        12. "ecs:tag/$TagKey": [
        13. "$TagValue"
        14. ]
        15. }
        16. }
        17. },
        18. {
        19. "Effect": "Allow",
        20. "Action": [
        21. "ecs:DescribeCloudAssistantStatus",
        22. "ecs:InstallCloudAssistant"
        23. ],
        24. "Resource": "acs:ecs:*:$AliyunMasterAccountID:instance/*",
        25. "Condition": {
        26. "StringEquals": {
        27. "ecs:tag/$TagKey": [
        28. "$TagValue"
        29. ]
        30. }
        31. }
        32. },
        33. {
        34. "Action": "ecs:DescribeTagKeys",
        35. "Effect": "Allow",
        36. "Resource": "*"
        37. },
        38. {
        39. "Action": "ecs:DescribeTags",
        40. "Effect": "Allow",
        41. "Resource": "*"
        42. },
        43. {
        44. "Effect": "Deny",
        45. "Action": [
        46. "ecs:DeleteTags",
        47. "ecs:UntagResources",
        48. "ecs:CreateTags",
        49. "ecs:TagResources"
        50. ],
        51. "Resource": "*"
        52. },
        53. {
        54. "Effect": "Allow",
        55. "Action": [
        56. "ecs:RunCommand"
        57. ],
        58. "Resource": "acs:ecs:*:$AliyunMasterAccountID:instance/*",
        59. "Condition": {
        60. "StringEquals": {
        61. "ecs:tag/$TagKey": [
        62. "$TagValue"
        63. ]
        64. }
        65. }
        66. },
        67. {
        68. "Action": [
        69. "ecs:RunCommand"
        70. ],
        71. "Resource": [
        72. "acs:ecs:*:$AliyunMasterAccountID:command/*"
        73. ],
        74. "Effect": "Allow"
        75. },
        76. {
        77. "Action": [
        78. "ecs:DescribeInvocations",
        79. "ecs:DescribeInvocationResults"
        80. ],
        81. "Resource": [
        82. "*"
        83. ],
        84. "Effect": "Allow"
        85. }
        86. ],
        87. "Version": "1"
        88. }
      10. 单击确定

      为子账号(或用户组)授权

      1. 使用阿里云主账号(或管理员账号)登录RAM控制台
      2. 在左侧导航栏的权限管理菜单下,单击授权
      3. 单击新增授权
      4. 被授权主体中输入子账号名称(或用户组名称)并选中,在选择权限中选择自定义权限策略,在左侧权限策略名称列表下,单击选择需要授予给子账号(或用户组)的权限策略。
        • 本文输入的子账号名称为subUser1ForOOS。
        • 本文选择的权限策略名称为OOSResourceManage和ECSResourceManage。
        • 在右侧区域框,若撤销对某策略的选择,可单击某条策略的×。oos
      5. 单击确定
      6. 单击完成

      执行OOS模版

      1. 使用主账号(或管理员账号)登录RAM概览,在右侧获取子账号登录地址。 oos
      2. 退出主账号(或管理员账号)的登录,访问刚获取的子账号登录地址,登录子账号。
        • 本文中登录的子账号为subUser1ForOOS。oos
      3. 登录成功后,进入运维编排控制台
      4. 单击我的模版,找到TestOperatingByGroups模版,单击创建执行oos
      5. 单击 下一步,设置参数
      6. 设置参数。
        • commandContent 将要执行的shell命令。
        • rateControl 模版执行时任务循环的并发速率及最大失败任务数。oos
      7. 单击 下一步,确认,单击创建执行
      8. 执行管理中可查看刚刚创建的执行。
        若创建执行成功,且执行状态处于运行中,则表示命令已开始执行。
      9. 当执行状态转换为成功时,则表示命令执行成功。
      10. 如需了解执行细节,单击该执行的详情,查看执行日志

      权限策略的补充验证

      OOS模版执行成功后,表示子账号subUser1ForOOS的权限策略支持模版TestOperatingByGroups执行需要的权限,也可在OOS控制台验证子账号无法执行未授权的模版。另外,若想验证子账号无法对标签以外实例的操作,可在登录子账号subUser1ForOOS后,通过Open Api 调试台对可操作API权限进行验证。

      附件:OOS模版- - -在某标签下的所有ECS实例中执行命令

      • 模板内容(YAML格式)
      1. FormatVersion: OOS-2019-06-01
      2. Description:
      3. en: Run a specific command in specified ECS instances.
      4. zh-cn: 对某些实例执行具体命令。
      5. name-en: RunSpecificCommandInSpecifiedInstances
      6. name-zh-cn: 指定的实例上执行具体命令。
      7. Parameters:
      8. commandContent:
      9. Description:
      10. en: commandContent.
      11. zh-ch: shell指令内容。
      12. Type: String
      13. Default: echo hello
      14. rateControl:
      15. Description:
      16. en: Concurrency rate of task execution.
      17. zh-cn: 任务执行的并发比率。
      18. Type: Json
      19. AssociationProperty: RateControl
      20. Default:
      21. Mode: Concurrency
      22. MaxErrors: 0
      23. Concurrency: 100%
      24. Tasks:
      25. - Name: CheckInstancesExistInTag
      26. Action: 'ACS::CheckFor'
      27. Description:
      28. en: Check for whether instances exist in specified tag.
      29. zh-cn: 验证指定标签下是否存在实例。
      30. Properties:
      31. Service: ECS
      32. API: DescribeInstances
      33. Parameters:
      34. Tags:
      35. - Key: TagKey
      36. Value: TagValue
      37. NotDesiredValues:
      38. - []
      39. PropertySelector: Instances.Instance|map(.InstanceId)
      40. Outputs:
      41. instanceIds:
      42. Type: List
      43. ValueSelector: 'Instances.Instance[].InstanceId'
      44. stoppedInstanceIdsExist:
      45. Type: String
      46. ValueSelector: >-
      47. Instances.Instance[]|select(.Status=="Stopped")|.InstanceId|[.]|all|tostring
      48. stoppedInstanceIds:
      49. Type: List
      50. ValueSelector: 'Instances.Instance[]|select(.Status=="Stopped")|.InstanceId'
      51. - Name: whetherStoppedInstancesExist
      52. Action: 'ACS::Choice'
      53. Description:
      54. en: Choose next task by stopped instances exist.
      55. zh-cn: 根据标签下是否存在停止的实例选择后续任务。
      56. Properties:
      57. DefaultTask: installCloudAssistant
      58. Choices:
      59. - When:
      60. 'Fn::Equals':
      61. - 'true'
      62. - '{{ CheckInstancesExistInTag.stoppedInstanceIdsExist }}'
      63. NextTask: startInstances
      64. - Name: startInstances
      65. Action: 'ACS::ECS::StartInstance'
      66. Description:
      67. en: Start Instance.
      68. zh-cn: 启动标签下停止的实例。
      69. Properties:
      70. instanceId: '{{ ACS::TaskLoopItem }}'
      71. Loop:
      72. Items: '{{ CheckInstancesExistInTag.stoppedInstanceIds }}'
      73. RateControl: '{{ rateControl }}'
      74. - Name: installCloudAssistant
      75. Action: 'ACS::ECS::InstallCloudAssistant'
      76. Description:
      77. en: Install cloud assostant for ECS instances in tag.
      78. zh-cn: 给标签下所有实例安装云助手。
      79. Properties:
      80. instanceId: '{{ ACS::TaskLoopItem }}'
      81. Loop:
      82. Items: '{{ CheckInstancesExistInTag.instanceIds }}'
      83. RateControl: '{{ rateControl }}'
      84. - Name: runCommand
      85. Action: 'ACS::ECS::RunCommand'
      86. Description:
      87. en: Run cloud assostant command on ECS instances in tag.
      88. zh-cn: 在标签下所有实例中运行云助手命令。
      89. Properties:
      90. commandContent: '{{ commandContent }}'
      91. commandType: RunShellScript
      92. instanceId: '{{ ACS::TaskLoopItem }}'
      93. Loop:
      94. Items: '{{ CheckInstancesExistInTag.instanceIds }}'
      95. RateControl: '{{ rateControl }}'
      96. Outputs:
      97. invocationOutputs:
      98. AggregateType: 'Fn::ListJoin'
      99. AggregateField: invocationOutput
      100. Outputs:
      101. invocationOutput:
      102. Type: String
      103. ValueSelector: .invocationOutput
      104. Outputs:
      105. invocationOutput:
      106. Type: List
      107. Value: '{{ runCommand.invocationOutputs }}'
      • 模板内容(JSON格式)
      1. {
      2. "FormatVersion": "OOS-2019-06-01",
      3. "Description": {
      4. "en": "Run a specific command in specified ECS instances.",
      5. "zh-cn": "对某些实例执行具体命令。",
      6. "name-en": "RunSpecificCommandInSpecifiedInstances",
      7. "name-zh-cn": "指定的实例上执行具体命令。"
      8. },
      9. "Parameters": {
      10. "commandContent": {
      11. "Description": {
      12. "en": "commandContent.",
      13. "zh-ch": "shell指令内容。"
      14. },
      15. "Type": "String",
      16. "Default": "echo hello"
      17. },
      18. "rateControl": {
      19. "Description": {
      20. "en": "Concurrency rate of task execution.",
      21. "zh-cn": "任务执行的并发比率。"
      22. },
      23. "Type": "Json",
      24. "AssociationProperty": "RateControl",
      25. "Default": {
      26. "Mode": "Concurrency",
      27. "MaxErrors": 0,
      28. "Concurrency": "100%"
      29. }
      30. }
      31. },
      32. "Tasks": [
      33. {
      34. "Name": "CheckInstancesExistInTag",
      35. "Action": "ACS::CheckFor",
      36. "Description": {
      37. "en": "Check for whether instances exist in specified tag.",
      38. "zh-cn": "验证指定标签下是否存在实例。"
      39. },
      40. "Properties": {
      41. "Service": "ECS",
      42. "API": "DescribeInstances",
      43. "Parameters": {
      44. "Tags": [
      45. {
      46. "Key": "TagKey",
      47. "Value": "TagValue"
      48. }
      49. ]
      50. },
      51. "NotDesiredValues": [
      52. []
      53. ],
      54. "PropertySelector": "Instances.Instance|map(.InstanceId)"
      55. },
      56. "Outputs": {
      57. "instanceIds": {
      58. "Type": "List",
      59. "ValueSelector": "Instances.Instance[].InstanceId"
      60. },
      61. "stoppedInstanceIdsExist": {
      62. "Type": "String",
      63. "ValueSelector": "Instances.Instance[]|select(.Status=="Stopped")|.InstanceId|[.]|all|tostring"
      64. },
      65. "stoppedInstanceIds": {
      66. "Type": "List",
      67. "ValueSelector": "Instances.Instance[]|select(.Status=="Stopped")|.InstanceId"
      68. }
      69. }
      70. },
      71. {
      72. "Name": "whetherStoppedInstancesExist",
      73. "Action": "ACS::Choice",
      74. "Description": {
      75. "en": "Choose next task by stopped instances exist.",
      76. "zh-cn": "根据标签下是否存在停止的实例选择后续任务。"
      77. },
      78. "Properties": {
      79. "DefaultTask": "installCloudAssistant",
      80. "Choices": [
      81. {
      82. "When": {
      83. "Fn::Equals": [
      84. "true",
      85. "{{ CheckInstancesExistInTag.stoppedInstanceIdsExist }}"
      86. ]
      87. },
      88. "NextTask": "startInstances"
      89. }
      90. ]
      91. }
      92. },
      93. {
      94. "Name": "startInstances",
      95. "Action": "ACS::ECS::StartInstance",
      96. "Description": {
      97. "en": "Start Instance.",
      98. "zh-cn": "启动标签下停止的实例。"
      99. },
      100. "Properties": {
      101. "instanceId": "{{ ACS::TaskLoopItem }}"
      102. },
      103. "Loop": {
      104. "Items": "{{ CheckInstancesExistInTag.stoppedInstanceIds }}",
      105. "RateControl": "{{ rateControl }}"
      106. }
      107. },
      108. {
      109. "Name": "installCloudAssistant",
      110. "Action": "ACS::ECS::InstallCloudAssistant",
      111. "Description": {
      112. "en": "Install cloud assostant for ECS instances in tag.",
      113. "zh-cn": "给标签下所有实例安装云助手。"
      114. },
      115. "Properties": {
      116. "instanceId": "{{ ACS::TaskLoopItem }}"
      117. },
      118. "Loop": {
      119. "Items": "{{ CheckInstancesExistInTag.instanceIds }}",
      120. "RateControl": "{{ rateControl }}"
      121. }
      122. },
      123. {
      124. "Name": "runCommand",
      125. "Action": "ACS::ECS::RunCommand",
      126. "Description": {
      127. "en": "Run cloud assostant command on ECS instances in tag.",
      128. "zh-cn": "在标签下所有实例中运行云助手命令。"
      129. },
      130. "Properties": {
      131. "commandContent": "{{ commandContent }}",
      132. "commandType": "RunShellScript",
      133. "instanceId": "{{ ACS::TaskLoopItem }}"
      134. },
      135. "Loop": {
      136. "Items": "{{ CheckInstancesExistInTag.instanceIds }}",
      137. "RateControl": "{{ rateControl }}",
      138. "Outputs": {
      139. "invocationOutputs": {
      140. "AggregateType": "Fn::ListJoin",
      141. "AggregateField": "invocationOutput"
      142. }
      143. }
      144. },
      145. "Outputs": {
      146. "invocationOutput": {
      147. "Type": "String",
      148. "ValueSelector": ".invocationOutput"
      149. }
      150. }
      151. }
      152. ],
      153. "Outputs": {
      154. "invocationOutput": {
      155. "Type": "List",
      156. "Value": "{{ runCommand.invocationOutputs }}"
      157. }
      158. }
      159. }

      ]]> 变更ECS实例规格_云服务器ECS_调用示例_Java SDK-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 变更ECS实例规格

      更新时间:2019-11-12 16:31:40

      本页目录

      本教程详细介绍如何使用Alibaba Cloud SDK for Java变更ECS实例规格(可修改公网宽带大小)。

      前提条件

      在使用本教程前,请确保已完成以下操作:

      • 使用Alibaba Cloud SDK for Java,您需要一个阿里云账号和访问密钥(AccessKey)。 请在阿里云控制台中的AccessKey管理页面上创建和查看您的AccessKey。
      • 确保您已经安装了Alibaba Cloud SDK for Java,准确的SDK版本号,请参见 阿里云开发工具包(SDK)
        <dependencies>
            <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-core -->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>4.4.3</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-ecs-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-ecs</artifactId>
                <version>4.17.4</version>
            </dependency>
        </dependencies>

      代码示例

      本文操作示例主要以代码形式体现,具体代码如下:

      import com.aliyuncs.DefaultAcsClient;
      import com.aliyuncs.IAcsClient;
      import com.aliyuncs.ecs.model.v20140526.DescribeResourcesModificationRequest;
      import com.aliyuncs.ecs.model.v20140526.DescribeResourcesModificationResponse;
      import com.aliyuncs.ecs.model.v20140526.DescribeResourcesModificationResponse.AvailableZone.AvailableResource.SupportedResource;
      import com.aliyuncs.ecs.model.v20140526.ModifyInstanceSpecRequest;
      import com.aliyuncs.ecs.model.v20140526.ModifyInstanceSpecResponse;
      import com.aliyuncs.exceptions.ClientException;
      import com.aliyuncs.profile.DefaultProfile;
      import com.google.gson.Gson;
      import java.util.List;
      
      /**
       * DescribeResourcesModification    查询升级和降配实例规格或者系统盘时,某一可用区的可用资源信息
       * ModifyInstanceSpec    调整一台按量付费ECS实例的实例规格和公网带宽大小
       */
      public class ModifyInstance {
      
          // 已关机状态的ECS实例
          private static String instancenId = "i-bp1gh*****";
      
          public static void main(String[] args) {
              Gson gson = new Gson();
              IAcsClient client = Initialization();
              // 查询某实例升级实例规则时的可用资源信息
              List<DescribeResourcesModificationResponse.AvailableZone> availableZones = DescribeResourcesModification(client);
              System.out.println("--------------------Available resource information for instance rules--------------------");
              System.out.println(gson.toJson(availableZones));
              // 获取实例的可用资源信息
              // (此处应根据实际情况或者结合前端业务来进行)
              SupportedResource supportedResource = availableZones.get(0).getAvailableResources().get(0).getSupportedResources().get(0);
              // 支持的可供创建的具体资源
              ModifyInstanceSpecResponse response = ModifyInstanceSpec(client, supportedResource.getValue());
              System.out.println("--------------------Instance specification changes successfully--------------------");
              System.out.println(gson.toJson(response));
          }
      
          /**
           * ModifyInstanceSpec    调整一台按量付费ECS实例的实例规格和公网带宽大小
           */
          private static ModifyInstanceSpecResponse ModifyInstanceSpec(IAcsClient client, String value) {
              ModifyInstanceSpecRequest request = new ModifyInstanceSpecRequest();
              // 指定的实例ID
              request.setInstanceId(instancenId);
              /**
               * 是否支持跨集群升级实例规格。默认值:false
               * 当参数AllowMigrateAcrossZone取值为true时,一旦您根据返回信息升级了云服务器,请留意以下注意事项:
               *
               * 经典网络类型实例:
               * 对于已停售的实例规格,非I/O优化实例变配到I/O优化实例时,实例私网IP地址、磁盘设备名和软件授权码会发
               * 生变化。对于Linux实例,普通云盘(cloud)会被识别为xvda或者xvdb等,高效云盘(cloud_efficiency)
               * 和SSD云盘(cloud_ssd)会被识别为vda或者vdb等。
               * 对于正常售卖的实例规格族,实例的私网IP地址会发生变化。
               *
               * 专有网络VPC类型实例:
               * 对于已停售的实例规格,非I/O优化实例变配到I/O优化实例时,云服务器磁盘设备名和软件授权码会发生变化。Linux
               * 实例的普通云盘(cloud)会被识别为xvda或者xvdb等,高效云盘(cloud_efficiency)和SSD云盘(cloud_ssd)
               * 会被识别为vda或者vdb等。
               */
              request.setAllowMigrateAcrossZone(false);
              request.setAsync(true);
              request.setInstanceType(value);
              try {
                  return client.getAcsResponse(request);
              } catch (ClientException e) {
                  System.out.println("ErrCode:" + e.getErrCode());
                  System.out.println("ErrMsg:" + e.getErrMsg());
                  System.out.println("RequestId:" + e.getRequestId());
                  throw new RuntimeException();
              }
          }
      
          /**
           * DescribeResourcesModification    查询升级和降配实例规格或者系统盘时,某一可用区的可用资源信息
           */
          private static List<DescribeResourcesModificationResponse.AvailableZone> DescribeResourcesModification(IAcsClient client) {
              DescribeResourcesModificationRequest request = new DescribeResourcesModificationRequest();
              //
              request.setDestinationResource("InstanceType");
              request.setResourceId(instancenId);
              try {
                  DescribeResourcesModificationResponse response = client.getAcsResponse(request);
                  return response.getAvailableZones();
              } catch (ClientException e) {
                  System.out.println("ErrCode:" + e.getErrCode());
                  System.out.println("ErrMsg:" + e.getErrMsg());
                  System.out.println("RequestId:" + e.getRequestId());
                  throw new RuntimeException();
              }
          }
      
      
          /**
           * Initialization  初始化公共请求参数
           */
          private static IAcsClient Initialization() {
              // 初始化请求参数
              DefaultProfile profile = DefaultProfile.getProfile(
                      "<your-region-id>", // 您的可用区ID
                      "<your-access-key-id>", // 您的AccessKey ID
                      "<your-access-key-secret>"); // 您的AccessKey Secret
              return new DefaultAcsClient(profile);
          }
      }
      				

      执行结果

      正确的返回结果类似如下:

      --------------------Available resource information for instance rules--------------------
      [
          {
              "regionId": "cn-hangzhou-dg-a01", 
              "zoneId": "cn-hangzhou-h", 
              "status": "Available", 
              "statusCategory": "WithStock", 
              "availableResources": [
                  {
                      "type": "InstanceType", 
                      "supportedResources": [
                          {
                              "value": "ecs.hfc5.large", 
                              "status": "Available", 
                              "statusCategory": "WithStock"
                          }, 
                          {
                              "value": "ecs.t5-c1m1.large", 
                              "status": "Available", 
                              "statusCategory": "WithStock"
                          }, 
                          {
                              "value": "ecs.ic5.3xlarge", 
                              "status": "Available", 
                              "statusCategory": "WithStock"
                          }, 
                          {
                              "value": "ecs.c5.large", 
                              "status": "Available", 
                              "statusCategory": "WithStock"
                          }, 
                          {
                              "value": "ecs.t5-c1m1.xlarge", 
                              "status": "Available", 
                              "statusCategory": "WithStock"
                          }
                      ]
                  }
              ]
          }
      ]
      --------------------Instance specification changes successfully--------------------
      {"requestId":"BAC795D9-D8F1-4E76-9F69-75EA47B5FD4B"}

      ]]> 指定ECS规格创建ECI_实例_弹性容器实例ECI-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 指定ECS规格创建ECI

      更新时间:2020-04-08 16:54:03

      本页目录

      在一些业务场景下,业务需要有特殊的规格需求,例如:GPU、增强的网络能力、高主频、本地盘等需求,ECI支持通过指定特定的ECS规格进行创建。ECI指定规格完全参考ECS规格定义。

      规格说明

      注意 在该模式下, ECI单价跟对应规格的ECS按量价格一致,按秒计费。

      您可以通过ECS 实例规格可购买地域总览,查询每个地域和可用区具体支持的ECS规格信息。

      目前支持的实例规格族包括:

      • 通用型(1:4)实例规格族 g6、g5、sn2ne(网络增强)

      • 计算型(1:2)实例规格族 c6、c5、sn1ne(网络增强)

      • 内存型(1:8)实例规格族 r6、r5、se1ne(网络增强)

      • 密集计算型(1:1)实例规格族 ic5

      • 高主频计算型(1:2)实例规格族 hfc6、hfc5

      • 高主频通用型(1:4)实例规格族 hfg6、hfg5

      • GPU计算型实例规格族gn6i、gn6v、gn5i、gn5(不支持本地存储)

      • 突发性能实例规格族 t6、t5

      Kubernetes方式

      容器服务支持指定ECS规格创建ECI创建ECI实例。

      1. 进入容器服务应用菜单,比如无状态,创建应用。

      2. 点击右上角,使用模板创建。

      3. 在pod模板里加入注解。

        注意 annotations需要添加到Pod的metadata中。

      apiVersion: apps/v1beta2 # for versions before 1.8.0 use apps/v1beta1
      kind: Deployment
      metadata:
        name: nginx-deployment-basic
        labels:
          app: nginx
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: nginx
        template:
          metadata:
            labels:
              app: nginx
            annotations:
              k8s.aliyun.com/eci-use-specs : "ecs.c5.large"  #根据需要替换 ECS 规格
          spec:
          #  nodeSelector:
          #    env: test-team
            containers:
            - name: nginx
              image: nginx:1.7.9 # replace it with your exactly <image_name:tags>
              ports:
              - containerPort: 80

      查看创建的容器组

      OpenAPI方式

      通过CreateContainerGroup OpenAPI进行实例创建时,可以通过InstanceType来指定规格,其他参数的使用请参考CreateContainerGroup接口描述。

      请求参数:

      名称 类型 是否必选 示例值 描述
      InstanceType String ecs.c5.xlarge 实例规格,可以通过“,”进行分隔传入最多5个值,比如“ecs.c5.xlarge,ecs.g5.xlarge”

      ]]> 后付费ECS询价示例_云产品询价参考文档_询价域_阿里云交易和账单管理API-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 后付费ECS询价示例

      更新时间:2020-04-13 17:35:59

      本页目录

      参考本示例查询后付费云服务器ECS的价格。

      询价说明

      获取后付费ECS实例的价格,您需要完成以下操作:

      1. 调用QueryProductList获取产品code,云服务器ECS的产品code为ecs
      2. 调用DescribePricingModule接口获取产品的配置参数。该接口须指定ProductCodeSubscriptionType参数。云服务器ECS的SubscriptionType取值为PayAsYouGo

        下表列举了获取后付费ECS实例价格需提供的参数。

        参数名称 示例值 说明
        Region cn-hangzhou 地域信息。
        ModuleList.1.ModuleCode InstanceType 实例规格。
        ModuleList.1.Config InstanceType:ecs.g5.xlarge,IoOptimized:IoOptimized,ImageOs:windows,InstanceTypeFamily:ecs.g5 实例类型取值。
        ModuleList.1.PriceType Hour 价格类型。
        ModuleList.2.ModuleCode SystemDisk 系统盘。
        ModuleList.2.Config SystemDisk.Category:cloud_efficiency,SystemDisk.Size:55 系统盘取值。
        ModuleList.2.PriceType Hour 价格类型。
        ModuleList.3.ModuleCode DataDisk 数据盘。
        ModuleList.3.Config DataDisk.Category:cloud_ssd,DataDisk.Size:130 数据盘取值。
        ModuleList.3.PriceType Hour 价格类型。
        ModuleList.4.ModuleCode InternetMaxBandwidthOut 公网带宽和类型。
        ModuleList.4.Config InternetMaxBandwidthOut:1024 公网带宽取值和类型取值:
        • 0:按固定单宽。
        • 1:按使用流量。
        默认为0。
        ModuleList.4.PriceType Hour 价格类型。
      3. 调用GetPayAsYouGoPrice接口获取后付费ECS实例的价格。

      请求示例(按固定单宽)

      https://business.aliyuncs.com/?Action=GetPayAsYouGoPrice
      &ProductCode=ecs
      &SubscriptionType=PayAsYouGo
      &Region=cn-qingdao
      &ModuleList.1.ModuleCode=InstanceType
      &ModuleList.1.Config=InstanceType:ecs.g5.xlarge,IoOptimized:IoOptimized,ImageOs:linux
      &ModuleList.1.PriceType=Hour
      &ModuleList.2.ModuleCode=SystemDisk
      &ModuleList.2.Config=SystemDisk.Category:cloud_efficiency,SystemDisk.Size:55
      &ModuleList.2.PriceType=Hour
      &ModuleList.3.ModuleCode=DataDisk
      &ModuleList.3.Config=DataDisk.Category:cloud_ssd,DataDisk.Size:130
      &ModuleList.3.PriceType=Hour
      &ModuleList.4.ModuleCode=InternetMaxBandwidthOut
      &ModuleList.4.Config=InternetMaxBandwidthOut:1024
      &ModuleList.4.PriceType=Hour
      &公共参数

      ]]> 金融云上ECS服务器的配置_常见问题_产品常见问题_金融云-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 金融云上ECS服务器的配置

      更新时间:2020-06-23 15:12:34

      本页目录

      本文将为您介绍阿里金融云上的ECS服务器的配置说明。

      云服务器ECS的开通

      云服务器ECS购买页,单击立即购买。开通ECS服务器时,有以下选项。

      选项 说明 建议选项
      地域 选择服务器所在的地域,金融云的生产服务器建议选择在杭州。杭州的ECS只能连接杭州的RDS,杭州的RDS是双机房高可用的。 建议将生产系统放在杭州地域。
      可用区 可用区相当于物理机房的概念,指的是所购买ECS的物理空间位于的哪个机房。 同一功能的服务器,一半选择在“杭州可用区B”,另一半选择在“杭州可用区C”。
      CPU/内存 选择服务器的配置。 根据需要选择。如果应用程序支持,建议选择多台低配置(而不是少量高配置)的服务器分布在两个可用区。
      公网带宽 如果ECS需要访问互联网上的资源,例如下载互联网上的文件、发送邮件、调用短信网关等,则需要在这里选择一个公网带宽。需要注意的是,即使这里选择了公网带宽,互联网用户还是无法通过ECS分配到的公网IP访问到ECS。 根据需要选择。
      镜像类型 公共镜像是阿里云提供的标准操作系统。 选择“公共镜像”。
      公共镜像 选择所需要的操作系统和版本。 根据需要选择。
      数据盘 给云服务器增加新的磁盘,添加后在云服务器的操作系统上看起来就是一块新硬盘。 根据需要添加并设置大小。
      登录密码 设置云服务器的登录密码。
      实例名称 对实例的描述。 建议设置为有意义的名称,便于以后管理。
      购买时长 实例购买多长时间。 按需要选择,一年的价格相当于一个月的价格乘10。
      数量 按以上的选择的购买数量。 按需要选择。
      金融云上ECS服务器的配置-1 金融云上ECS服务器的配置-2金融云上ECS服务器的配置-3

      选择完成后,单击立即购买,也可单击加入清单然后再单击批量购买完成购买。

      金融云上ECS服务器的配置-4金融云上ECS服务器的配置-5

      云服务器ECS的管理

      云服务器ECS可通过管理控制台进行管理,在访问管理控制台之前,必须要先连接管理VPN。

      在云服务器ECS的管理界面上,可以对服务器进行停止、重启、升级、更换操作系统等操作,ECS的具体操作请参见帮助文档

      安全组配置

      1. 安全组配置需要进入“云盾”,在产品与服务菜单中单击云盾金融云上ECS服务器的配置-6
      2. 如果看到的是新版云盾,请单击右上角的反馈旧版按钮。金融云ECS服务器的配置-7
      3. 选择服务详情 > 防火墙管理金融云上ECS服务器的配置-8
      4. 创建安全组。金融云上ECS服务器的配置-9
      5. 添加规则。金融云上ECS服务器的配置-10金融云上ECS服务器的配置-11

      搭建金融云推荐架构

      金融云推荐架构对应的ECS部分实现方法如下。

      1. 将生产服务器加入清单

        在ECS购买页面上,选择杭州可用区B > 选择适应的配置和服务器数量 > 加入清单 > 杭州可用区C > 加入清单。现在,购买清单中包含两个可用区的多台同配置服务器。

      2. 将堡垒机加入清单

        在ECS购买页面上,选择杭州可用区B,选择1C/1G内存的ECS一台,操作系统与生产服务器一致,公网带宽选择为0,单击“加入清单”再选择杭州可用区C,单击“加入清单”。现在,购买清单中包含两个可用区各一台堡垒机。

      3. 完成购买

        在页面上单击批量购买,完成付款。然后进入ECS管理控制台,记录所有服务器的内网IP地址。

      4. 配置堡垒机安全组

        创建一个安全组,名称为sg-bastion,将规则中加入“我的VPN”中列出的所有客户端IP地址(见3.2.2),端口设置为22(堡垒机是Linux)或3389(堡垒机是Windows)。所属服务器选择为2台堡垒机。

      5. 配置生产服务器安全组

        创建一个安全组,名称为sg-production,将规则中加入2个堡垒机的IP地址,端口设置为22(堡垒机是Linux)或3389(堡垒机是Windows)。所属服务器选择为所有的生产服务器。

      6. 其它配置

        如果有需要,再创建测试服务器和测试服务器安全组。如果创建了新的服务器,需要手工添加到相应的安全组后才可被堡垒机访问。

      最终实现的访问路径如下图。

      金融云上ECS服务器的配置-11

      常见问题

      • 问:什么叫可用区?如何选择可用区?

        答:可用区相当于物理机房的概念,指的是所购买ECS物理位于的哪个机房。同一功能的服务器,一半选择在一个可用区,另一半选择在同一地域的另一个可用区。

      • 问:ECS上的公网带宽有什么用?

        答:如果ECS需要访问互联网上的资源,比如下载互联网上的文件、发送邮件、调用短信网关等,则需要选择公网带宽。需要注意的是,即使这里选择了公网带宽,互联网用户还是无法通过ECS分配到的公网IP访问到ECS。

      • 问:我VPN登录后,无法ping通我的ECS

        答:ECS默认不允许ping操作,建议通过堡垒机去ping。如果想从VPN客户端直接ping,可创建一个新的安全组,名称为sg-icmp,入站规则处,选择ICMP协议,端口任意填入一个数字,源地址写为VPN登录后分配的IP地址,然后单击“添加规则”,并在“所属服务器”上选中想要ping的服务器,之后单击“应用所有规则”。

        金融云上ECS服务器的配置-12
      • 问:无法登录ECS 服务器

        答:首先你要登录VPN,请参见前面的VPN相关内容。然后ECS默认不允许登录,需要通过云盾配置防火墙,允许SSH协议或RDP协议后才可以登录ECS。SSH使用TCP协议22端口,RDP使用TCP协议3389端口。配置的源IP地址要配置为VPN自服务控制台中看到的“客户端IP列表”。建议通过堡垒机登录,请参见上面的内容实现堡垒机。

      • 问:互联网用户无法访问到我的ECS

        答:互联网用户需要通过SLB才能访问到ECS,具体请参见SLB的配置方法。

      • 问:如何上传下载文件

        答:上传文件之前,请先通过安全组开通远程管理端口。Linux操作系统请使用sftp协议进行文件上传(可使用图形化的sftp协议工具winscp,参见winscp官方网站,Windows操作系统请使用远程桌面的连接本地驱动器功能(参见这里这里)。也可以开通一台ECS服务器的公网带宽,然后通过外部的云盘服务进行数据中转,推荐使用淘盘

      ]]> 批量释放ECS实例_实例运维_最佳实践_运维编排服务-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 批量释放ECS实例

      更新时间:2019-10-30 10:06:13

      本页目录

      运维编排服务OOS为批量释放实例的场景提供了公共模版。只需输入需要被释放的实例ID,就可以达到释放所有实例的操作,避免手动操作单独释放实例。

      前提条件

      确保需被释放的实例类型属于按量付费预付费类型。

      操作步骤

      1、 首先登录到运维编排控制台
      2、单击公共模版
      3、 根据实例付费情况,在搜索框内搜索ACS-ECS-BulkyDeleteInstances(批量删除按量付费实例)模版或ACS-ECS-BulkyDeletePrepaidInstances(批量删除预付费实例),对其单击创建执行ACS-ECS-BulkyDeleteInstances
      _2019_10_29_10_24_42ACS-ECS-BulkyDeletePrepaidInstances
      1571896019420_c332dd12_f873_4281_8e80_a79a1e7eb427
      4、 单击 下一步,设置参数1571896245786_d11c0ccd_7956_45f9_83cc_580624bb1be1
      5、 输入参数公共模版参数:ACS-ECS-BulkyDeleteInstances

      1571974257999_0be5318a_de73_43c2_a6be_eb3a4f4fe166

      参数 说明 示例
      target 选择被处理的示例标签 / ECS实例 ID test: oos / i-bp10jvphtux8ad**
      force 是否强制删除实例 true/false
      rateControl 速率控制类型 并发控制
      并发速率 10
      最大错误次数 10
      执行使用到的权限的来源 可选参数。
      - (默认设置)当前账号的已有权限:执行您使用的账号的权限动作。请确保您拥有创建自定义镜像涉及的所有ECS API调用权限。
      - 指定RAM角色,使用该角色的权限:如果指定了RAM角色名称,OOS扮演该RAM角色执行运维任务。
      当前账号的已有权限

      oos

      6、 单击 下一步,确认 ,单击 确认风险并执行1572243372654_90e80ced_345e_42c3_b1b8_5fb08a415fb2

      7、 在执行管理中可查看刚刚创建的执行,若创建执行成功,且执行状态处于运行中,则表示实例处于删除中。1572243761013_85b0eb6c_2af4_48c2_9c10_ab68df7e126c
      8、 状态为成功时,表示实例全部被删除。1572243761013_85b0eb6c_2af4_48c2_9c10_ab68df7e126c

      9、 查询实例是否被删除1572243865751_b301e02e_2491_48c9_aba4_5565a6c8cbca

      附录1:公共模版和背后逻辑

      • 批量删除非预付费实例(ACS-ECS-BulkyDeleteInstances
        1. FormatVersion: OOS-2019-06-01
        2. Description:
        3. en: Bulky delete ECS postpaid instances.
        4. zh-cn: 批量删除ECS按量付费实例。
        5. name-en: ACS-ECS-BulkyDeleteInstances
        6. name-zh-cn: 批量删除ECS实例
        7. categories:
        8. - instance_manage
        9. Parameters:
        10. targets:
        11. Type: Json
        12. AssociationProperty: Targets
        13. AssociationPropertyMetadata:
        14. ResourceType: 'ALIYUN::ECS::Instance'
        15. force:
        16. Description:
        17. en: Whether to force the release of a running instance.
        18. zh-cn: 是否强制释放正在运行的实例。
        19. Type: Boolean
        20. Default: false
        21. rateControl:
        22. Description:
        23. en: Concurrency ratio of task execution.
        24. zh-cn: 任务执行的并发比率。
        25. Type: Json
        26. AssociationProperty: RateControl
        27. Default:
        28. Mode: Concurrency
        29. MaxErrors: 0
        30. Concurrency: 10
        31. OOSAssumeRole:
        32. Description:
        33. en: The RAM role to be assumed by OOS.
        34. zh-cn: OOS扮演的RAM角色。
        35. Type: String
        36. Default: OOSServiceRole
        37. RamRole: '{{ OOSAssumeRole }}'
        38. Tasks:
        39. - Name: getInstance
        40. Description:
        41. en: Views the ECS instances.
        42. zh-cn: 获取ECS实例。
        43. Action: 'ACS::SelectTargets'
        44. Properties:
        45. ResourceType: 'ALIYUN::ECS::Instance'
        46. Filters:
        47. - '{{ targets }}'
        48. Outputs:
        49. instanceIds:
        50. Type: List
        51. ValueSelector: 'Instances.Instance[].InstanceId'
        52. - Name: deleteInstance
        53. Action: 'ACS::ExecuteAPI'
        54. Description:
        55. en: Deletes ECS instance with the specified instance ID.
        56. zh-cn: 通过指定实例ID删除实例。
        57. Properties:
        58. Service: ECS
        59. API: DeleteInstance
        60. Parameters:
        61. InstanceId: '{{ ACS::TaskLoopItem }}'
        62. Force: '{{ force }}'
        63. Loop:
        64. RateControl: '{{ rateControl }}'
        65. Items: '{{ getInstance.instanceIds }}'

      该模板顺序执行以下任务:

      1. 获取目标实例或实例上的Tags。
      2. 批量删除实例
      • 批量删除预付费实例(ACS-ECS-BulkyDeletePrepaidInstances
      1. FormatVersion: OOS-2019-06-01
      2. Description:
      3. en: >-
      4. Bulky delete prepaid instances.The specified ECS instances must be in
      5. stopped status.
      6. zh-cn: 批量删除ECS预付费实例。指定的ECS实例必须处于已停止状态。
      7. name-en: ACS-ECS-BulkyDeletePrepaidInstances
      8. name-zh-cn: 批量删除ECS预付费实例
      9. categories:
      10. - instance_manage
      11. Parameters:
      12. targets:
      13. Type: Json
      14. AssociationProperty: Targets
      15. AssociationPropertyMetadata:
      16. ResourceType: 'ALIYUN::ECS::Instance'
      17. rateControl:
      18. Description:
      19. en: Concurrency ratio of task execution.
      20. zh-cn: 任务执行的并发比率。
      21. Type: Json
      22. AssociationProperty: RateControl
      23. Default:
      24. Mode: Concurrency
      25. MaxErrors: 0
      26. Concurrency: 10
      27. OOSAssumeRole:
      28. Description:
      29. en: The RAM role to be assumed by OOS.
      30. zh-cn: OOS扮演的RAM角色。
      31. Type: String
      32. Default: OOSServiceRole
      33. RamRole: '{{ OOSAssumeRole }}'
      34. Tasks:
      35. - Name: getInstance
      36. Description:
      37. en: Views the ECS instances.
      38. zh-cn: 获取ECS实例。
      39. Action: 'ACS::SelectTargets'
      40. Properties:
      41. ResourceType: 'ALIYUN::ECS::Instance'
      42. Filters:
      43. - '{{ targets }}'
      44. Outputs:
      45. instanceIds:
      46. Type: List
      47. ValueSelector: 'Instances.Instance[].InstanceId'
      48. - Name: modifyInstanceChargeType
      49. Action: 'ACS::ExecuteAPI'
      50. Description:
      51. en: Modify the charge type for prepaid instances to postpaid.
      52. zh-cn: 修改预付费实例付费方式为按量付费。
      53. Properties:
      54. Service: ECS
      55. API: ModifyInstanceChargeType
      56. Parameters:
      57. InstanceIds: '{{ getInstance.instanceIds }}'
      58. InstanceChargeType: PostPaid
      59. - Name: deleteInstance
      60. Action: 'ACS::ECS::DeleteInstance'
      61. Description:
      62. en: Deletes the ECS instance by instance ID.
      63. zh-cn: 通过实例ID删除实例。
      64. Properties:
      65. instanceId: '{{ ACS::TaskLoopItem }}'
      66. Loop:
      67. RateControl: '{{ rateControl }}'
      68. Items: '{{ getInstance.instanceIds }}'

      该模板顺序执行以下任务:

      1. 获取目标实例或实例上的Tags。
      2. 将预付费实例修改为按量付费实例
      3. 批量删除实例

      ]]> 伸缩组内ECS实例的生命周期_ECS实例_实例管理_弹性伸缩-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 伸缩组内ECS实例的生命周期

      更新时间:2020-06-15 15:14:19

      编辑 我的收藏

      新浪微博 微信 钉钉

      本页目录

      本章节介绍伸缩组内ECS实例的生命周期管理方式,并列出可能的服务状态。弹性伸缩管理ECS实例的生命周期时,会检查ECS实例是否健康,并及时移出甚至释放不健康的ECS实例。

      伸缩组内ECS实例的生命周期管理方式

      根据ECS实例加入伸缩组的方式,ECS实例可以分为自动创建的ECS实例和手动创建的ECS实例,对应的生命周期管理方式如下表所示。
      ECS实例类型 加入方式 生命周期管理方式
      自动创建的ECS实例 根据伸缩组的实例配置信息来源自动创建的ECS实例。 弹性伸缩管理ECS实例的全生命周期,弹性扩张时负责创建ECS实例,弹性收缩时负责停止和释放ECS实例。
      手动创建的ECS实例 由您手动创建,然后手动添加至伸缩组的ECS实例。 由是否将ECS实例的生命周期托管给伸缩组决定:
      • 已托管给伸缩组:弹性收缩时负责停止和释放ECS实例。
      • 未托管给伸缩组:弹性收缩时负责将ECS实例移出伸缩组,但不会释放ECS实例。

      伸缩组内ECS实例的服务状态

      从加入伸缩组到移出伸缩组,一台ECS实例可能的服务状态如下表所示。
      服务状态 说明
      加入中(Pending) ECS实例正在加入伸缩组,包括加入负载均衡实例的后端服务器、RDS实例的访问白名单等过程。
      加入挂起中(Pending:Wait) 如果伸缩组内创建了适用于弹性扩张活动的生命周期挂钩,ECS实例在加入伸缩组时被挂起并等待挂钩超时时间结束。
      服务中(InService) ECS实例已成功加入伸缩组并正常提供服务。
      备用中(Standby) ECS实例不提供服务,负载均衡权重被置为零,且弹性伸缩不管理ECS实例的生命周期,而是由您手动管理。
      保护中(Protected) ECS实例正常提供服务,但弹性伸缩不管理ECS实例的生命周期,而是由您手动管理。
      停用中(Stopped) ECS实例已停机,不提供服务。
      移出中(Removing) ECS实例正在移出伸缩组,包括移出负载均衡实例的后端服务器、RDS实例的访问白名单等过程。
      移出挂起中(Removing:Wait) 如果伸缩组内创建了适用于弹性收缩活动的生命周期挂钩,ECS实例在移出伸缩组时被挂起并等待挂钩超时时间结束。

      伸缩组内ECS实例的健康检查

      弹性伸缩管理伸缩组内ECS实例的生命周期时,会定期检查ECS实例的运行状态,如果发现一台ECS实例未处于运行中状态,则判定该ECS实例不健康。

      说明 ECS实例的运行状态是ECS实例从创建开始到释放结束的可能状态,并非ECS实例在伸缩组内的服务状态,详情请参见实例生命周期介绍

      弹性伸缩会移出伸缩组中不健康的ECS实例,具体动作如下:

      • 如果ECS实例由弹性伸缩自动创建,或者由您手动添加至伸缩组但生命周期已托管给伸缩组,弹性伸缩会移出并释放ECS实例。
      • 如果ECS实例由您手动添加至伸缩组且生命周期未托管给伸缩组,弹性伸缩会从伸缩组移出ECS实例,但不会释放该ECS实例。

      移出不健康的ECS实例时不受最小实例数的限制,可能导致组内实例数低于最小实例数。此时弹性伸缩会自动创建对应数量的ECS实例,使得组内实例数等于最小实例数。

      警告 请确保账号可用额度充足。如果账号欠费,所有后付费的ECS实例(包括按量付费实例和抢占式实例)都会停机,甚至被释放。欠费后伸缩组内ECS实例状态变化,请参见欠费说明

      ]]> 华为云ECS实例迁移至阿里云ECS实例_各源环境的迁移_最佳实践_服务器迁移中心-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 华为云ECS实例迁移至阿里云ECS实例

      更新时间:2020-06-16 18:46:12

      本页目录

      您可以参考本文档中的步骤,将华为云ECS实例迁移至阿里云ECS实例。

      华为ECS Windows系统迁移至阿里云

      在迁移之前,请先完成以下准备工作:

      • 创建快照以备份数据。
      • 确保系统时间与所在地域的标准时间一致。
      • 确保您的待迁移虚拟机出方向能访问下列网址及端口。
        • 服务器迁移中心SMC所需的https://smc.aliyuncs.com:443端口。
        • 迁移过程中,连接中转实例所需的端口8080和8703。

          说明 迁移过程中,SMC自动创建、启动、停止和释放中转实例No_Delete_SMC_Transition_Instance。中转实例的默认安全组在入方向开放了8080和8703端口,这是中转实例的迁移服务端口。

      • 检查系统中是否安装了QEMU Guest Agent VSS Provider服务。

        如果已安装该服务,您可以尝试去类似于C:Program Files (x86)virtiomonitor的目录下找到并执行uninstall.bat脚本,卸载QEMU Guest Agent软件。

      • 检查并确保Windows系统VSS服务为启动状态 。
      • 检查授权应用。源服务器迁移到阿里云后,系统底层硬件设备会发生变化,可能会导致一些跟硬件绑定的应用许可证(license)失效,您需要做好检查。
      • 检查授权应用。源服务器迁移到阿里云后,系统底层硬件设备会发生变化,可能会导致一些跟硬件绑定的应用许可证(license)失效,您需要做好检查。

      完成以上操作后,即可进行服务器迁移操作,详情请参见迁移流程概述

      华为ECS Linux系统迁移至阿里云

      在迁移之前,请先完成以下准备工作:

      • 创建快照以备份数据。
      • 确保系统时间与所在地域的标准时间一致。
      • 确保您的待迁移虚拟机出方向能访问下列网址及端口。
        • 服务器迁移中心SMC所需的https://smc.aliyuncs.com:443端口。
        • 迁移过程中,连接中转实例所需的端口8080和8703。

          说明 迁移过程中,SMC自动创建、启动、停止和释放中转实例No_Delete_SMC_Transition_Instance。中转实例的默认安全组在入方向开放了8080和8703端口,这是中转实例的迁移服务端口。

      • 请确保已安装Rsync库。安装方式请参见如何安装Rsync
      • 检查授权应用。源服务器迁移到阿里云后,系统底层硬件设备会发生变化,可能会导致一些跟硬件绑定的应用许可证(license)失效,您需要做好检查。
      • 如果是第一次进行服务器迁移,建议您先使用测试机演练。

      完成以上操作后,即可进行服务器迁移操作,详情请参见迁移流程概述

      ]]> AWS EC2迁移至阿里云ECS_各源环境的迁移_最佳实践_服务器迁移中心-阿里云 Fri, 20 Jun 2025 02:20:34 +0800 AWS EC2迁移至阿里云ECS

      更新时间:2020-06-17 09:42:24

      本页目录

      您可以参见本文档中的步骤,将AWS EC2实例迁移至阿里云ECS实例。

      EC2 Windows实例迁移至阿里云

      在迁移之前,请先完成以下准备工作:

      • 创建快照以备份数据。
      • 确保系统时间与所在地域的标准时间一致。
      • 检查授权应用。源服务器迁移到阿里云后,系统底层硬件设备会发生变化,可能会导致一些跟硬件绑定的应用许可证(license)失效,您需要做好检查。
      • 检查网络环境。
        • 如果是跨国际地域,由于网络环境较不稳定,操作步骤请参见跨国际地域迁云的操作步骤
        • 如果您的网络可以打通VPC内网,建议在创建迁移任务时网络模式选择内网传输。使用内网传输能获得比通过公网更快速更稳定的数据传输效果,提高迁移工作效率。详情请参见步骤二:创建并启动迁移任务
      • 检查并确保Windows系统VSS服务为启动状态 。
      • 检查是否安装了QEMU Guest Agent软件。如果安装了此工具软件,您需要先卸载。卸载的具体步骤,请参见Windows服务器卡在Prepare For Rsync Disk 0阶段,怎么办
      • 如果是第一次进行服务器迁移,建议您先使用测试机演练。

      完成以上操作后,即可进行服务器迁移操作,详情请参见迁移流程概述

      EC2 Linux实例迁移至阿里云

      在迁移之前,请先完成以下准备工作:

      • 创建快照以备份数据。
      • 确保系统时间与所在地域的标准时间一致。
      • 检查授权应用。源服务器迁移到阿里云后,系统底层硬件设备会发生变化,可能会导致一些跟硬件绑定的应用许可证(license)失效,您需要做好检查。
      • 检查网络环境。
        • 如果是跨国际地域,由于网络环境较不稳定,操作步骤请参见跨国际地域迁云的操作步骤
        • 如果您的网络可以打通VPC内网,建议在创建迁移任务时网络模式选择内网传输。使用内网传输能获得比通过公网更快速更稳定的数据传输效果,提高迁移工作效率。详情请参见步骤二:创建并启动迁移任务
      • 检查cloud-init。

        cloud-init服务是众多云平台用于自动初始化配置系统的服务软件,但AWS和阿里云的cloud-int服务配置无法完全兼容。从AWS迁移过来的系统可能会因为cloud-init启动失败导致无法正常启动,网络无法正常连通。建议您在迁移前使用阿里云的cloud-init配置,具体操作步骤请参见安装cloud-init,或者卸载原cloud-init服务。

      • 检查GRUB引导程序。

        Amazon Linux系列系统必须升级GRUB至2.02及以上,部分低内核系统(如CentOS/Red Hat 5和Debian 7)需要升级GRUB至1.99及以上。具体操作,请参见如何为Linux服务器安装GRUB

        说明 请使用root权限升级GRUB引导程序。

      完成以上操作后,即可进行服务器迁移操作,详情请参见迁移流程概述

      跨国际地域迁云的操作步骤

      1. 将AWS EC2实例迁移到阿里云对应的国际地域,具体操作步骤请参见迁移流程概述

        例如,EC2实例位于美国,您可以将其迁移至阿里云位于美国的地域。具体地域及地域ID请参见地域(Region)

      2. 将新建的镜像复制到目标阿里云地域。具体操作步骤,请参见复制镜像
      3. 使用该镜像在目标阿里云地域创建实例。具体操作步骤,请参见使用自定义镜像创建实例

      后续步骤

      AWS系统的SSH一般默认关闭root密码登录,您可以使用源AWS系统用户名和SSH Key登录阿里云的实例。

      ]]> 阿里云新品发布会周刊第57期 丨 助力企业打造专属钉钉 Fri, 20 Jun 2025 02:20:34 +0800 点击订阅新品发布会

      新产品、新版本、新技术、新功能、价格调整,评论在下方,下期更新!关注更多新品发布会!


      精品直播

      第97期:专属钉钉解决方案全新上线

      直播时间:2020年6月17日 15:00-16:50 预约直播
      TB1AK1TKrr1gK0jSZR0XXbP8XXa-780-388.jpg

      钉钉的用户数量已经突破3亿,客户对钉钉提供企业版的诉求越来越强。针对企业级共性需求,客户可按需选择专属设计、专属存储、专属安全和最高级别的专属App,定制出千人千面的钉钉,助力企业打造自己的钉钉。更多内容尽在直播间!

      讲师介绍:

      议题1《专属钉钉解决方案重磅发布》

      司酒-阿里云专属钉钉高级架构师

      议题2《专属钉钉解决方案v2.0核心能力介绍》

      毅珩-阿里云高级解决方案工程师

      热门阅读

      1、浅谈异地多活及阿里云容灾经验分享

      9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

      异地多活,英文Multi-Site High Availability,顾名思义就是分布在异地多个站点同时对外提供服务。与传统灾备最主要的区别是“多活”里所有站点都是同时在对外提供服务的,具体有以下几点不同:

      • 传统的灾备中心平时不提供服务,关键时刻无法确定切换到灾备中心是否可以切换成功。
      • 传统的灾备中心平时不提供服务,整个灾备资源会处于浪费状态,成本比较高。
      • 传统的灾备中心平时不提供服务,所以平时提供服务的数据中心还停留在单地域,当业务体量大到一定程度时,这种模式无法解决单地域资源瓶颈的问题。
        因为通过传统的灾备手段无法解决上述问题,阿里巴巴经过多年研究,成功在2013年的双十一实现了“丝般柔顺”的用户体验后,“异地多活”这项基础技术首次在业界亮相。

      查看原文

      2、阿里高可用架构建设实践经验

      9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

      随着业务在线化互联网化的高速发展,企业对核心业务系统的稳定性、可靠性、有效性、业务连续性等有了更高的要求。采用高可用系统架构支持重要系统、为关键业务提供7x24的不间断服务,已经成为众多企业保障业务稳定、持续运转的主要选择。但如何从海量实践中提炼出值得借鉴复制的高可用架构之道,实现适合自身的高可用系统架构,是需要企业深思熟虑的问题。 查看原文

      3、阿里云发布新一代云原生产品,加速企业向现代 IT 架构演进

      9989c5b90f96dedb20d3e717592eeed2c54bdb86.jpeg

      近日在阿里云线上峰会上,云原生应用平台产品总监赵林(丹臣)发表了《云原生 2020 新产品发布 传统应用架构往现代应用架构快速演进的基础设施》的主题演讲,详细介绍了阿里云全新发布的容器、中间件、Serverless 等产品。随着数字经济的快速发展和扩张,越来越多的企业开始采用云原生计算的思想和技术,以主导企业的数字化转型架构。 查看原文

      往期回顾

      第96期:企业出海全球化网络新品发布会 活动页面 直播视频

      中国企业全球化正当时,在国内人口红利见顶,国家政策支持的双背景之下,“全球化”被越来越多的企业视为发展新赛道。出海浪潮下,企业如何利用云服务快速占据先机?本议题将为您分享阿里云对出海战略的洞察,详解阿里云网络服务如何全力支持各领域海外业务拓展。

      第95期:阿里云EDAS 3.0版重磅升级发布会 活动页面 直播视频

      EDAS3.0在微服务治理、K8s集群纳管和监管控一体化、生态集成等基础能力上进行了全面的升级。本次发布会,EDAS资深技术专家和资深产品专家将重磅发布EDAS3.0版本,同时还邀请了安利和云途时代分享EDAS业务实践,讲述数字化转型实战故事。

      第94期:阿里云政企安全加速解决方案全新发布 活动页面 直播视频

      阿里云政企安全加速解决方案是依托阿里云全球分布的加速节点、领先的安全防护及等保合规等能力,打造的边缘安全和加速一站式服务,解决政府、金融、传媒、传统企业内容分发安全和加速性能的问题,为云上业务保驾护航!

      第93期:机器学习PAI DSW2.0 & Alink商业版新品发布会 活动页面 直播视频

      PAI起初是一个定位于服务阿里集团的机器学习平台,致力于让AI技术更加高效、简洁、标准的被公司内部开发者使用。对集团内,PAI服务了淘宝、支付宝、高德等部门的业务。随着PAI的算法的不断积累,2015年底PAI作为天池大赛的官方比赛平台在阿里云正式上线,也成为了国内最早的云端机器学习平台之一。随着PAI在阿里云的业务的不断发展,2018年PAI平台正式商业化,目前已经在公有云积累了数万的企业客户以及个人开发者,是目前国内领先的云端机器学习平台之一。

      第92期:云服务器ECS内存增强型实例re6全新发布 活动页面 直播视频

      实例是能够为您的业务提供计算服务的最小单位,不同的实例规格可以提供的计算能力不同。本章节为您介绍在售的所有ECS实例规格族,包括每种实例规格族的特点、在售规格和适用场景。根据业务场景和使用场景,ECS实例可以分为多种实例规格族。根据CPU、内存等配置,一种实例规格族又分为多种实例规格。ECS实例规格定义了实例的基本属性:CPU和内存(包括CPU型号、主频等)。但是,ECS实例只有同时配合块存储、镜像和网络类型,才能唯一确定一台实例的具体服务形态。

      第91期:5大安全产品全面升级在线发布 活动页面 直播视频

      IT基础设施云化和业务全面上云,企业在享受数字红利的同时,安全也变得更为重要。阿里云5款产品全面升级,从混合云安全、容器安全、身份管理等多个主流安全维度全面提升产品能力,让企业能够快速利用云原生安全优势,提升自身安全体系建设,同时也能快速应对疫情多变性,满足数字化发展长远安全需求。

      第90期: 数据库自治服务DAS重磅新品发布会 活动页面 直播视频

      数据库自治服务DAS基于机器学习及专家经验,真正实现数据库的自感知、自修复、自优化、自运维及自安全,以及数据库问题从发现、根因分析、修复、跟踪的诊断闭环。DAS的发布,标志着阿里云数据库迈入了自治时代,将为业务提供7*24的稳定、高效、智能的数据库服务。

      第89期: Serverless工作流重磅发布 活动页面 直播视频

      对于分布式应用和微服务而言,往往涉及构建复杂的、多步骤的、有状态的和长时间运行的业务流程。本次发布会将向大家隆重介绍阿里云Serverless工作流,致力于简化开发和运行业务流程所需要的任务协调、状态管理以及错误处理等繁琐工作。此外,该服务可协调各类分布式组件,减少流程代码量,在实现丰富控制逻辑的同时提高应用容错性,其自动扩展助力客户免于管理硬件预算和扩展。

      第88期:分析型数据库AnalyticDB for MySQL基础版重磅发布 活动页面 直播视频

      AnalyticDB for MySQL作为新一代云原生敏捷数据仓库,旨在帮助用户快速构建一套高性能、高可用、高可靠、高弹性和智能化的云数据仓库,更好的实现用户数据价值在线化。本次分享主要针对AnalyticDB for MySQL基础版特性以及用户如何0门槛和超低成本构建实时数据仓库。

      第87期:云数据库SQL Server 2019新版本发布会 活动页面 直播视频

      RDS SQL Server作为一款重要的关系型数据库产品,它以优秀的性能、可靠的稳定性以及高安全性被广大客户广泛用于生产系统中,阿里云RDS SQL Server拥有完整的产品形态和版本,部署形态从基础版、高可用版到集群版;规格上从独享型、通用型到共享型,从单租户到多租户;并以此构建完整的生命周期管理、备份还原和智能化运维体系。最新推出了经济易用的重磅产品和特性,这里将会有详细的解读。

      资料下载

      开放下载!新品发布产品资料

      最新产品发布资料大合集 ,以及你想了解的讲师PPT都可点击下载!

      产品动态

      新功能/版本:

      1、 CDN - CDN流量包推出1个月及6个月规格 查看产品 产品文档

      将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。
      解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。
      CDN/全站加速流量包限时特惠,50TB仅需4999元/年

      2、 DataV数据可视化 - 全新三维地形可视化神器-新版三维平面地图上线 查看产品 产品文档

      DataV数据可视化是使用可视化应用的方式来分析并展示庞杂数据的产品。DataV旨让更多的人看到数据可视化的魅力,帮助非专业的工程师通过图形化的界面轻松搭建专业水准的可视化应用,满足您会议展览、业务监控、风险预警、地理信息分析等多种业务的展示需求。

      3、 Web应用防火墙 - Web应用防火墙自定义Web规则组优化升级(WAF-V5.2.2.0) 查看产品 产品文档

      对网站或者APP的业务流量进行恶意特征识别及防护,将正常、安全的流量回源到服务器。避免网站服务器被恶意入侵,保障业务的核心数据安全,解决因恶意攻击导致的服务器性能异常问题。详询95187-1
      【”迎考“等保2.0】点击领取阿里云等保大礼包

      扫描二维码加入社区圈
      5555

      阿里云新品发布会交流群:群号(30675445)点击加入

      ]]>
      如何在VMware中进行创建CentOS虚拟机 Fri, 20 Jun 2025 02:20:34 +0800 今天给大家分享如何在VMware中创建CentOS虚拟机,CentOS6.7为例进行说明,CentOS7版本亦可以参考该教程,具体的教程如下。

      1、之后打开VMware,主页面如下图所示。点击第一个框框,“创建新的虚拟机”。

      如何在VMware中进行创建CentOS虚拟机
      2、弹出下图界面,选择第二个“自定义(高级)”,之后“下一步”。

      3、这一步默认即可,直接点击“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      4、这里点击“稍后安装操作系统”,之后选择“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      5、这一步是选择版本。客户机操作系统默认选择为Linux系统,而版本则根据电脑的位数来进行选择,如果电脑是64位系统的话,就选择CentOS64位,如果是32位的话,就直接选择CentOS即可。这里如果选择不正确的话,后面在安装Ubuntu系统的时候重启会报系统不兼容的错误。选择好版本之后,点击选择“下一步”。

      6、弹出虚拟机的名称和安装位置。

      如何在VMware中进行创建CentOS虚拟机
      7、虚拟机名称可以自定义,不多赘述,这里将其命名为master,后期作为集群的管理节点用的。虚拟机蛮占内存的,所以安装位置不建议放到C盘,建议放到其他的盘内,且要保证该磁盘的空间至少要大于虚拟机的大小。这里,小编将虚拟机放到E盘。

      如何在VMware中进行创建CentOS虚拟机
      8、处理器的配置。如果只是想拿虚拟机练练手,要求不高的话,就直接默认,选择“下一步”即可。

      9、如果对处理要求较高,则需要按需进行分配。这里小编将配置给的比较高,因为后期有其他的用途,如下图所示。

      如何在VMware中进行创建CentOS虚拟机
      10、虚拟机内存默认为2G。同上一步一致,要求不高的话直接默认即可。在这里小编选择了20G,大家可以根据自己的要求进行选择,要保证磁盘大小够用噢,之后选择“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      11、网络连接部分,直接默认即可,选择NAT模式,之后选择“下一步”。

      12、选择I/O控制器类型,这一步默认即可,选择推荐的类型,之后选择“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      13、磁盘类型一般选择推荐的就可以,SCSI和SATA都是比较受欢迎的磁盘类型,之后选择“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      14、选择磁盘。建议选择第一项,“创建新虚拟磁盘”,之后选择“下一步”。下一步最大磁盘大小默认设为20G,默认即可。这里选择“将虚拟磁盘存储为单个文件”,也有的伙伴选择“将虚拟磁盘拆分为多个文件”,也是可以的。

      如何在VMware中进行创建CentOS虚拟机
      15、指定磁盘文件。这一步默认即可,之后选择“下一步”。

      如何在VMware中进行创建CentOS虚拟机
      16、之后弹出下图的界面,之后再选择“完成”。

      如何在VMware中进行创建CentOS虚拟机
      17、至此,CentOS虚拟机创建完成,如下图所示。

      如何在VMware中进行创建CentOS虚拟机
      接下来你就可以在VMware虚拟机中安装CentOS系统了,Linux搞起来。

      想学习更多Python网络爬虫与数据挖掘知识,可前往专业网站:http://pdcfighting.com/

      ]]>
      探讨一下如何打造稳定的报名系统之考务管理报名系统开发设计浅谈 Fri, 20 Jun 2025 02:20:34 +0800 首先,标题已注明是浅谈,也就不会太高深,适合各种技术类、非技术类人员参阅。由于才疏学浅,可能存在不到之处,还请大神指正,勿喷、谅解。

      随着各地人事机构招考需求不断增强,规模不断扩大,考务管理工作难度不断加大。尤其是网上报名阶段,考生们犹如蓄势待发、抢滩登陆的士兵,扛着枪、冲锋号一响,同一时间往上冲,按照惯例配置的服务器自然难以承受这种猛烈的攻势。瞬时间崩塌,是显然的。

      所谓的惯例配置,就是通常情况下,我们会根据网站的日常访问流量,测算出所需要的服务器资源,然后按此配置去组建服务器。影响较大的是:带宽、CPU和内存。这三样相互依存,难分先后,任一样达不到要求,崩了即是可以遇见的。

      再有,就是软件的系统开发,有没有做代码优化、数据库优化、并发压力测试等。这些也都将直接影响到系统运行的稳定性。

      既然,都已经了解到问题所处的原因。为何一到省考、大考,系统就经常性会崩呢?

      这里,我认为不外乎有以下几种情况:

      1)软件层面:报名系统开发较早,没有预想到如今所要面对的峰值流量,系统更新迟缓或无更新;

      2)硬件层面:服务器为单位机房部署,可能配置较低,或存在多应用共存,难以确保其稳定性;

      3)维护层面:技术人员响应不及时,无法第一时间处理宕机故障。

      既然如此,更新升级不就完了。但对于大量的单位用户来说,系统升级谈何容易?需求要层层上报,预算要一步步审批,很多时候自然也就搁置了。

      实际上,在此之前我对于考试报名系统也未曾了解多少,可能还不及你。

      只是年前才开始接触报名系统的开发。而在开发期间,最主要的精力,都放在了上面我所描述的几个问题中。目前,我们的报名系统的开发已经过内测、公测,已进入正式部署阶段。而且也有了一定的用户案例。

      借此机会,给大家分享一下我们开发的报名系统(虽然现在互联网发达,传播速度超乎想象。但是,传播是快了,声音却越来越小...)

      这里,我只介绍我们的报名系统是如何解决这些问题的。

      首先,我们的系统包含了招聘公告发布、网上报名、资格审查、在线缴费、考场设置、座位自动编排、准考证生成及打印、成绩查询、面试通知单、短信群发、数据导出Excel、自动生成人才库信息等覆盖人事考试(报名)与考务管理全套业务流程的功能

      **划重点:超级计算集群。
      **
      官方用语:使用高速RDMA网络互联的CPU以及GPU等异构加速设备,面向高性能计算、数据分析,提供极致计算性能和并行效率的计算集群服务。分钟级交付的弹性裸金属实例,根据需求快速增减集群节点。

      实际解决的问题就是:报名系统不再崩!!!

      01bd225dc90c14a801209e1f32e283.jpg

      下面是我们系统特有的一些亮点功能,在此以动图的形式,简单展示下。

      1 一键批量生成考场:

      根据各岗位报考的人数,系统可自动以批量形式生成各个考场,可自定义考场座位数,亦支持手动模式。
      01abdc5dc90c3ca801209e1f9bf3eb.gif

      **2 自动生成留验单/桌贴/门贴:
      **
      自动编排考场以后,系统已为您生成各个考场的留验单、桌贴、门贴。完全不需要人工干预,即可实现。并且,支持在线一键打印,可自定义A3/A4或其他尺寸的纸张。
      01d0d85dc90c4da801209e1f4d6645.gif

      **3 考务通知,短信群发:
      **
      系统目前已支持多种考务通知类型,只需将考生加入短信群发列表,即可快速将考务通知以短信形式发送至考生手机。短信系统会根据考生所属运营商,自动选择短信发送通道,优化短信通知效率,以确保100%送达。
      013dbb5dc90c5ba8012163ba8009ff.gif

      **4 考务统计与汇总:
      **
      考务统计支持单个考试统计,亦可将各个考试合并统计,满足不同的统计需求。在汇总统计状态下,还可以手动进行选择,可精确到岗位统计。
      0159325dc90c70a8012163ba367cc8.gif

      系统的报名的流程:考生注册-设置密码-登录系统-选择考试-填报职位-报考信息-上传照片-资格审查-交费-打印准考证。

      系统链接(有兴趣的童鞋可以去参观下):
      人事考试网上报名考务管理系统

      ]]>
      flutter网络dio框架公共请求参数、请求header使用总结 Fri, 20 Jun 2025 02:20:34 +0800 题记
      —— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。

      重要消息


      本文章将讲述
      1.get请求中配置公共参数
      2.post请求配置公共参数
      3.请求header配置

      1 引言

      在实际应用开发中,我们会有像 token、appVersionCode 等等这些每个接口请求都需要传的参数 ,称之为公共请求参数,公共请求参数配置方式总结有三:

      • 在get与post请求时将参数配置进去,也可以通过请求header配置
      • 通过请求header将参数配置进去
      • 通过拦截器将请求参数配置到参数配置中或者是header中
      2 dio 配制公共请求参数

      那么在这里 dio 的请求中我们可以考虑这样来配制:

        String application = "V 1.2.2";
        int appVersionCode = 122;
        ///[url]网络请求链接 
        ///[data] post 请求时传的json数据
        ///[queryParameters] get请求时传的参数
        void configCommonPar(url,data,Map<String, dynamic> queryParameters){
          ///配制统一参数
          if (data != null) {
            data['application'] = application;
            data['appVersionCode'] = appVersionCode.toString();
          } else if (queryParameters != null) {
            queryParameters['application'] = application;
            queryParameters['appVersionCode'] = appVersionCode.toString();
          } else {
            ///url中有可能拼接着其他参数
            if (url.contains("?")) {
              url += "&application=$application&appVersionCode=$appVersionCode";
            } else {
              url += "?application=$application&appVersionCode=$appVersionCode";
            }
          }
        }
      }
      
      3 dio 配制Content-Type 与请求 header

      我们在创建 Dio对象时,会初始化一个 BaseOptions 来创建 Dio

            BaseOptions options = BaseOptions();
            ///请求header的配置
            options.headers["appVersionCode"]=406;
            options.headers["appVersionName"]="V 4.0.6";
            
            options.contentType="application/json";
            options.method="GET";
            options.connectTimeout=30000;
            ///创建 dio
            Dio dio = new Dio(options);

      我们也可以在每次发送 get 、post 等不同的请求时,通过 dio 获取到 默认的 options 然后修改一下

      void getRequestFunction2() async {
          ///用户id
          int userId = 3;
          ///创建 dio
          Dio dio = new Dio();
      
          ///请求地址
          ///传参方式1
          String url = "http://192.168.0.102:8080/getUser/$userId";
          ///在这里修改 contentType
          dio.options.contentType="application/json";
          ///请求header的配置
          dio.options.headers["appVersionCode"]=406;
          dio.options.headers["appVersionName"]="V 4.0.6";
          ///发起get请求
          Response response = await dio.get(url);
      
        ...
        }

      完毕

      在这里插入图片描述

      ]]>
      即时通讯(im)框架/系统开发思考(1)-通讯协议选型 Fri, 20 Jun 2025 02:20:34 +0800 1.前言:

      近来笔者接到公司的一个IM开发需要,要在原来的Web业务系统、移动端系统上加入一个即时聊天的功能,具有就是能聊天就行。相信各位也会接到需要开发IM的系统的任务,那么,开发一个im系统应选用哪种通讯协议?

      2.思考-即时聊天选用的通讯协议选哪种:

      XMPP(可扩展消息与存在协议): 是一个成熟开源的即时聊天协议, 基于XMPP的开源技术有openfire(后台端), spark(客户端)
      优势: 比较成熟的聊天协议,已在XMPP协议内支持单聊, 群聊, 加好友等功能.满足基本的需要.
      缺点: 基于xml 实现的通讯协议, 消息载体比较重, 增加网络流量, 定制困难,需要了解openfire原理, 改造有一定的难度。
      跨平台: 一般, 仅基于java平台, 如要实现web 等需要自己使用socket进行xml封装.

      MQTT(消息队列遥测传输): 严格来说, MQTT是使用与物联网领域的消息传输协议,但有一些即时通讯系统也使用这个协议进行拓展开发,故拎出来说说。MQTT主要有三个特点: 1. 基于发布订阅模式; 2. 为网络不可靠的环境下提供一套消息重传协议.3. 基于TCP/IP, 消息载体轻, 耗电量小.
      优势: 已实现消息丢失重传功能, 消息载体轻, 耗电量小.
      缺点: 不是专用于聊天的协议, 登录, 单聊, 群聊, 加好友等功能都需要自己实现. 开发成本高,如要支持多个平台, 每个客户端都需要定制,IM方面的开源社区不活跃,技术文档少。
      跨平台: 差, 每个客户端都需要实现MQTT的聊天协议。

      自定义协议: 可以基于WebSocket, socket.io, 甚至常用的消息队列: RabbitMQ, RocketMQ 等长连接框架上加入聊天的业务, 比如登录, 单聊, 群聊, 加好友等功能。
      优势: 基于自己熟悉的技术栈,易上手。
      缺点: 单聊, 群聊, 加好友等功能都需要自己实现。
      跨平台: 一般, 若选用广泛使用于客户端的WebSocket, socket.io,能很好的实现跨平台性。

      考虑到日后的跨平台定制开发,最终敲定选用socket.io + 自定义协议实现。为什么要选用socket.io?不用WebSocket?

      1. socket.io设计的目标是支持任何的浏览器,任何设备。在接口方面,socket.io统一了通信的API,在内部实现上支持WebSocket,AJAX long-polling, AJAX multipart streaming, Forever Iframe等方式。也就是说,Socket.io会根据环境来选择适合的通信方式。
      2. socket.io 支持namespace, room 等概念,可以很方便的对socket 进行分组。
      3. socket.io 具有自动重连的功能,适用于恶劣的网络环境。

      而e聊sdk正是基于socket.io上开发的免费开源即时通讯框架,e聊sdk 已实现了多平台的socket.io 支持(如:Web, ReactNative, 微信小程序等), 阅读e聊客户端核心sdk 的源码中, 可见到在socket.ts 文件中已实现了多平台的socket.io 支持:

              /*IFTRUE_WEBAPP*/
              const webio = require('socket.io-client');
              this。socket = webio。connect(url+"");
              /*FITRUE_WEBAPP*/
      
              /*IFTRUE_RNAPP*/
              const rnio = require('socket.io-client');
              this。socket = rnio。connect(url+"", {transports: ['websocket']});
              /*FITRUE_RNAPP*/
      
              /*IFTRUE_WXAPP*/
              const wxio = require('weapp.socket.io');
              this。socket = wxio。connect(url+"");
              /*FITRUE_WXAPP*/

      正是由于e聊sdk在设计之初已具有良好的跨平台支持, 选择使用e聊开发即时通讯,可以在跨平台上实现事半功倍的效果。

      参考:
      e聊客户端核心sdk源码

      ]]>
      iNeuOS工业互联网平台,热力云管理控制平台 Fri, 20 Jun 2025 02:20:34 +0800 本项目属于威海某热电公司,所属区域供热面积达1200万平米,需要采集的热量表及阀门数量为6万余块,通讯服务器与1100多台设备进行数据采集和控制,在应用iNeuKernel设备容器框架进行二次开发,每天上报数据率达100%,服务器负载大大降低(CPU利用率在10%以下),长期稳定运行。二次开发调试过程简单清晰。

      图片.png
      图片.png
      图片.png

      图片.png

      物联网&大数据技术 QQ群:54256083
      物联网&大数据合作 QQ群:727664080
      网站:http://www.ineuos.net
      联系QQ:504547114
      合作微信:wxzz0151

      ]]>
      面向零售业的AI驱动的视频分析 Fri, 20 Jun 2025 02:20:34 +0800 AI-Driven-Video-Analytics-for-Grocery-Stores-1068x656-1.jpg

      人工智能(AI)与数据科学直接相关,后者旨在从一系列信息中提取业务价值。 该价值可以包括扩展预测能力,规律知识,明智的决策,降低成本等。换句话说,人工智能以大量信息运行,分析输入数据,并根据这些信息开发自适应解决方案。


      在现代世界,零售业正在迅速增加人工智能在所有可能的工作流程中的应用。因此,通过应用分析来利用机会无疑可以改进食品杂货行业的各种操作。有了人工智能,最大的连锁超市实现了雄心勃勃的目标:


      1)改善和扩展客户服务能力


      2)自动化供应链计划和订单交付


      3)减少产品浪费


      4)加强对缺货和库存过多的管理


      5)加强需求预测


      人工智能解决方案的生态系统是广泛的,能够满足所有杂货店零售商的大多数需求(从大型连锁店到最小的企业)。到目前为止,在隔离期间,在线商品分析已经成为管理缺货情况的真正“救世主”。通过智能数据驱动的方法,超市可以处理大量的信息,准确预测消费者的需求和供应库存,并生成最准确的价格和购买建议。因此,即使在冠状病毒大流行等最危急的情况下,杂货零售商也将继续盈利。话虽如此,很明显,所有公司现在都需要针对COVID-19立即采取行动计划。


      视频监控的新水平


      通常,大多数杂货店都具有连续的视频监视系统。以前,此类系统的安装仅出于安全目的:控制产品的安全性并防止盗窃。但是现在,人工智能视频分析能够监视客户从进入商店到付款的整个过程。它是如何工作的,为什么商店需要它?


      像亚马逊和沃尔玛这样的大型连锁超市使用高科技相机,利用自动物体识别(automatic object identification)技术。这种系统通常用于无人驾驶的电动汽车上,通过计算机监控乘客行为并处理视觉信息。但商场视频分析的主要目标是确定哪些商品有很高的需求,哪些产品的购买者最常回到货架上,等等。此外,相机还可以识别人脸,确定顾客的身高、体重、年龄和其他身体特征。随后,人工智能(基于所有获得的数据)从特定的消费者群体中识别出最受欢迎的产品,并提供更改定价策略的选项。计算机自动完成所有这些过程,不需要人工干预。


      防止商品缺货


      零售业中的人工智能能够解决人们无法应对的问题。一个人实际上无法观看所有视频监控,没有足够的时间进行此操作,而且人类的视觉并不完美。但这不再是必需的!商场的视频分析可完美应对此类任务。例如,将摄像头连接到商店的自动化仓库系统,并在货架上配备传感器,可以发现库存记录中的漏洞,促进调查。商场数据分析还可以监视库存并提供有关补货需求的信号。如上所述的面部识别技术能够将人的面部与罪犯(或通缉犯)的面部进行比较,并警告安全人员。


      促进人流和商店布局


      收集的有关客户行为的数据可帮助超市经理优化商店布局。此外,计算机程序可以设计最佳的布局并对其进行测试,从而产生总体上更好的客户体验,并增加商店的利润。


      可以收集有关进入商店的人数以及他们花费的购物时间的数据。基于这些数据,人工智能可以预测人流量的大小和人们排队等候的时间。这将有助于改善客户服务并减少员工成本。 换句话说,AI能够在一天的各个小时制定最佳的商店管理计划,从而为企业带来最大的收益。 例如:


      1)优化展示位置和平面布置


      2)改善策略性人员分配


      3)在停留时间内和购买之间得出相关性


      4)预测各个购物群体的产品


      增强客户体验


      每个企业都应尽可能了解其受众,以提供最佳服务。商场中的AI使用视频智能软件提供详细的人口统计数据,并详细分析购物习惯。这些信息为商场提供了无限的机会来增加利润。通过了解他们的顾客,商场经理可以最大化顾客的购物体验,创造有利条件(专门针对顾客的喜好)。此外,用于杂货店的AI可以帮助产生给定目标市场的最准确的需求预测模型。


      除了与目标受众合作之外,管理人员还可以使用从视频分析获得的数据将信息传输到营销部门。通过探索其他受众,营销人员可以制定策略,通过创建相关的广告、促销和销售来吸引新客户。此外,商场可以为小型购物群体创建单独的展示柜(纯素食产品或无麸质产品),以满足他们的需求。


      在商场所有现有的人工智能技术中,视频内容分析可在几乎所有活动中提供最大的支持:销售,营销,广告和布局策略。通过优化这些流程,商场不仅可以节省和减少损失,还可以通过增加利润来扩展业务。主要目标不仅是要满足客户需求,而且要提高客户保留率。


      原文链接 ]]>
      3个因素看透 AI 技术架构方案的可行性 Fri, 20 Jun 2025 02:20:34 +0800

      --------点击屏幕右侧或者屏幕底部“+订阅”,关注我,随时分享机器智能最新行业动态及技术干货----------

      image.png

      人工智能这几年发展的如火如荼,不仅在计算机视觉和自然语言处理领域发生了翻天覆地的变革,在其他领域也掀起了技术革新的浪潮。无论是在新业务上的尝试,还是对旧有业务对改造升级,AI 这个奔涌了 60 多年的“后浪”,正潜移默化的影响着我们传统的技术架构观念。

      AI 架构(尤其是以机器学习和深度学习为代表的架构方案)已经成为我们技术架构选型中的一个新的选项。

      你是否需要 AI 架构的解决方案?AI 架构选型的主要依据是什么?这是我们今天主要讨论的问题。

      我们先来看一个典型的 AI 架构:

      image.png

      • 1、首先需要采集训练模型所需要的数据,这些数据有可能来自业务系统本身,如 CTR 预估任务中的用户点击数据、用户下单数据等;也有可能来系统外部,公开购买或自主爬取,如图片分类任务中的图片、NLP 任务中的语料等。
      • 2、这些数据被收集起来后,经过清洗、加工,被存储起来,因为毕竟不是只用一次。一般是存储在分布式存储设备(如 HDFS)或云端,多数公司还会建立自己的数据平台,保存在数据仓库中,长期积累下来。
      • 3、需要使用的时候,先进行数据筛选,选择合适的特征数据,然后经过数据预处理,送入到算法模型中。模型的搭建可选的技术框架很多,可以是基于 spark mllib,也可以是 sklearn、tensorflow、pytorch 等。然后经过训练、评估和调参,完成模型的构建工作。
      • 4、最后模型要应用到线上的具体业务中,完成分类、回归某一具体任务。在部署过程中,有可能是将模型打包,将预测模型直接部署到业务系统(客户端)中;也有可能是直接提供一个在线 RESTful 接口,方便跨语言调用。

      总结一下,经过数据采集、加工处理、特征选择、数据预处理、模型训练、模型评估、模型应用几个环节,数据跨过业务系统、数据平台、算法模型三个系统,形成一个闭环,最终又应用到业务系统中,这就构成了整个 AI 架构的核心。

      是否需要 AI 架构,如何衡量这套技术架构方案的可行性?我认为,主要是看以下三个要素。

      1. 场景

      我们讨论架构的可行性,是否适合业务及业务发展是第一衡量准则,AI 架构也不例外。

      回顾那些经典的、已经广泛应用的机器学习场景,比如推荐、搜索、广告等,这些场景都具有这样的特点:场景相对封闭、目标单一、可控。

      究其原因,无论算法模型多么复杂,其最终都要落实到损失函数上的,而后者一般都是单目标、单优化任务。或追求极值(损失最小化)、或达到某种对抗上的平衡(比如GAN)。在这种情况下,无论业务如何建模,还是要落地到算法模型和损失函数的,最终也就限制了场景和目标上的单一。

      因此,看一个业务是否适合AI架构,就要先看这个业务场景目标是否单一、可控。或经过业务建模和架构拆解后,每个环节的场景是否单一。

      举个例子,同程艺龙酒店系统为酒店商家提供了上传酒店图片的功能,在这个场景下,除了要审查图片的合法性,还要给图片打上分类标签,如“大堂”、“前台”、“客房”、“周边”等。为了能正常使用AI架构,就必须对场景内的各目标进行拆分,训练不同的分类器。具体流程如下:

      image.png

      其中,第2、3、4步涉及到多个图片分类器,每个分类器的目标不同,所需要的训练数据也不同。对于输入的同一个样本图片,每个分类器完成自己的职能,目标单一可控。对于一些不通过的样本,可能还涉及到人工干预。最后合法的图片存入系统。

      从业务必要性上来说,也并不是所有业务场景都需要AI架构。算法模型是对事物的精确模拟和抽象,复杂度也是比较高的。但可能有时我们业务上并不需要如此精细的控制。比如有时一个简单的if...else...就解决了问题;复杂点的可能会设计几种“策略”,然后由业务专家针对每种情况进行配置;再复杂的可能还会考虑BI的方案:收集数据,然后展开多维度的分析,最后由分析师连同业务专家得到某种规律性的结论,再内置到系统里,效果可能也不错。

      再举个酒店分销调价的例子,在将酒店分销给代理售卖前,一般会在底价基础上对产品卖价进行干预,调整一定的点数(百分比),保证销量的同时,最大化收益。

      一开始,可能仅仅是一个固定的比率(比如加价6%)。随着业务发展,设计了一系列策略,比如针对“是否独家”、“是否热门”2维度将酒店划分到4个象限里,对“独家-热门”酒店实施一个较高的调价比率,而对“非独家-冷门”酒店实施一个较低的比率。结果收益提高了一大截,效果不错。

      而后,业务人员希望施行更加精细的控制,于是对酒店的星级、地区、商圈、独家、房型等维度进行了更为精细的划分,并结合历史数据进行统计分析,对各种结果施以不同的调价比率。产量和收益又进一步提升了。

      这时如果各业务方都比较满意、成本也不高,系统复杂度也不高,那就没必有再考虑更为精细、智能的AI架构了。引入AI,本质上,还是要带来效率、体验或准确性的提升,同时平衡成本和收益,控制系统复杂度。如果不能带来这些,那就要重新审视我们的方案了。

      当然,有时我们也会考虑架构的扩展性和业务的发展,预留一些设计上的“开闭”空间。“策略模式”这时也许是个不错的选择。对于系统的默认策略,采用基于人工的、配置的方案,同时保留策略扩展接口,随着将来业务要求的增高,再引入“基于AI的策略”。这样即控制了当前的成本,又平衡了系统的扩展性。

      2. 数据

      数据决定了机器学习的上限,而算法和模型只是逼近这个上限而已。

      数据的采集和获取通常需要很长时间,建立充分、全面的数据仓库,更需要长时间的积累和打磨,因此,数据在任何一个公司都是宝贵的资产,不肯轻易送出。而一个算法模型的成功与否,关键看数据和特征。因此,一套 AI 架构的解决方案,最终能否取得好的效果,关键看是否已经采集到了足够、充分的数据。

      这些数据来源一般包括:自有系统采集、互联网公开数据收集(或爬取)、外购等。

      自有系统采集是最常见的方案,业务系统自身产生的数据,一般也更适合业务场景的应用。可这样的数据珍贵且稀少,所以往往需要公司的决策者提前布局,早早的开始收集、整理业务数据,建设数据平台、充实数据仓库,这样经过几个月甚至几年以后,在真正用到AI架构时,弹药库里已经储备了充足的“弹药”了。

      互联网公开的数据爬取也是一个快速且免费的方法,但在茫茫大海中找到适合自己的数据并不容易,且因为你能拿到、别人也能拿到,因此很难拉开和其他竞对公司的差异。

      外购一般要花费巨额费用,且质量参差不齐,一般是互联网公司最后不得已的方案。

      在数据获取成本高、难度大、积攒时间久这样的前提下,而场景又适合使用 AI 架构,面对数据匮乏,是不是就没有办法了呢?也不尽然,我们还是有些替代方案的。

      • 1、 浅层模型通常比深层模型需要更少的数据量,因此,在数据量不足的时候,通常可以使用浅层模型替代深层模型来减少对数据量的需求。当然,模型的表达能力也会随之下降,但应对不是特别复杂的业务场景,浅层模型也一样能取得很好的效果。当然,随之而来的是对特征挖掘更高的要求和对模型选择的挑剔。拿分类任务来说,SVM、逻辑回归、随机森林、朴素贝叶斯...每种模型都有其特点和适用性,要充分考虑和权衡,才能利用好每一条数据。所谓数据不够、模型来凑,也是不得已的办法。
      • 2、 采用预训练模型也是降低数据需求量的一个很好的办法,迁移学习已经在图像分类问题上广泛运用, BERT 模型也将预训练模型带入自然语言处理的大门。在一些特定问题上,如果能找到合适的预训练模型,再加之少量自己的数据进行微调,不但对数据的需求量降低,训练时间也大大降低,一举两得。只是合适的预训练模型可遇而不可求。
      • 3、 还有一个减少数据需求的变通的办法是采用少量数据先“启动”,然后不断获取数据,并加快模型更新频率,直至采用“在线学习”的方法。这里实际上是将总的数据需求,拉长到时间维度去解决。当然,这里也需要业务上允许前期模型的准确度不是那么高,随着数据的增多和模型的不断更新,逐步达到预期效果。
      • 举个例子,酒店 shopper 类产品的售卖,为了加快展现速度,通常采取供应商数据预抓取的方式落地。但供应商给的 QPS 极其有限,每次只能抓取一个酒店,高频率的抓取可以保证酒店数据的新鲜度,给客人更好的体验;低频率的抓取因库存、价格信息时效性不能保证,往往就会导致预定失败,造成损失。因此,如何在酒店间合理的分配 QPS 就是一个典型的机器学习问题。
      • 我们从酒店热度、预定周期、节假日等多个维度进行了特征挖掘,最后却发现“季节”这个关键因素,我们却提取不到有效特征,原因是数据仓库里只有三个月的数据,也就是只有当季的数据。
      • 为了解决这个问题,我们重新设计了模型,调整了架构方案,采用“在线学习”的方式,将模型更新问题纳入到了解决方案中。原始数据只用来训练一个初始模型,上线后,模型不断拿新产生的数据并进行迭代更新,同时对时间线更近的数据赋以更高的样本权重,以此来保证对季节性因素的跟进。系统上线后,取得了很好的效果。
      • 4、 强化学习在初始数据缺乏的情况下,大多数时候也是一个备选方案。强化学习采用“试错”的方式,不断演化,并最终学到规律。当然这需要业务模型做相应的调整,同时,如果演化周期过长,那有可能模型在前期相当长的时间内,都不能做出较优的决策,因此需要业务容忍度较高。

      3. 算力

      众所周知,训练过程是一个典型的“计算密集型任务”,没有强大的算力,是难以支撑算法模型的训练和研究的。做机器学习的计算平台,GPU 几乎是标配,其训练时间比 CPU 一般能缩短 5 倍以上。

      目前,主要有自建和租赁云平台两种途径获取。如果“不差钱”,当然可以选择自建,但现在 GPU 升级换代太快,基本一年一换。对于做机器学习的 GPU 来说,运算速度是关键,很可能花了大价钱搭建的 GPU 集群,过几年却变成了一台“老爷车”。

      租赁云平台虽然可以随时享受最新 GPU 运算速度带来的“快感”,但所需花费的精力也不少。不但要详细对比每家云平台提供的服务和成本,还要合理的搭配 CPU和 GPU,做到资源利用最大化。

      说了这么多,提的最多的可能就是“成本”和“收益”这两个词了,这也是业务最关心的问题。无论是计算资源还是系统架构,上一套 AI 架构的解决方案都是需要投入相当大的成本的,如果选择得当,在一个合适的场景下,AI 也是能带来相当不错的收益;但如果入不敷出,选择 AI 架构的解决方案就要慎重了。

      最后,技术人员储备和法律因素也是上AI架构前需要考量的问题,前阵子还发生了国家工信部约谈AI换脸应用企业的事件。

      AI 是一场浪潮,它不仅带来了新的技术和行业,也给了老系统焕发新生命活力的机会。作为技术人员,我们不仅要拥抱新技术带来的挑战,更要清楚其技术选型的主要因素和背后的风险,这样才能屹立浪潮之巅。那么,你是否需要 AI 架构的解决方案呢?

      image.png

      原文链接:https://yqh.aliyun.com/detail/14534

      ]]>
      SLS机器学习最佳实践:时序相似性分析 Fri, 20 Jun 2025 02:20:34 +0800 一、使用场景

      今天给大家介绍几个好用的时序相似性分析函数,具体涉及到的函数包含:时序的聚类,相似性计算等。主要解决的场景如下:

      • 当您有N台机器的监控指标数据时,想快速知道在某一段时间机器的CPU形态的大致呈现哪些形态,便于用户更好的了解当前系统的状态;
      • 当您指定某一台机器的某指标曲线时,您想知道哪些机器的相同指标与当前指定的曲线更加相似;
      • 当您人工输入一条时序曲线(网站整体的访问延迟曲线),您想知道是哪个服务的访问延时的变化规律同当前指定的曲线十分相似,便于缩小问题的排查;
        上述场景问题均可以归纳成,时序聚类(按照形态、按照数值),时序相似性判别这两个问题。

      二、函数介绍

      SLS平台提供了两个函数供大家使用,具体的文档地址可以参考下:https://help.aliyun.com/document_detail/93235.html

      ts_density_cluster
      ts_hierarchical_cluster

      其中第一个函数主要是针对曲线的形态进为标准进行聚类,其底层的核心聚类算法是DBSCAN密度聚类;第二个函数主要针对针对原始曲线的相似性进行聚类,更多的会考虑曲线之间的欧式距离等因素,其底层的核心聚类算法是层次聚类;具体的原理介绍可以参考之前的文章,大家也可以去网上查阅相关资料自行阅读。接下来主要给大家介绍下在SLS中该怎么使用这些函数。

      三、案例实战

      3.1 数据探索

      • query-01
      * | select DISTINCT index_name, machine, region from log
      • query-02
      * | select count(1) as num from (select DISTINCT index_name, machine, region from log)
      • query-03
      * and index_name : load | 
      select 
        __time__, 
        value, 
        concat(
          region, '#', machine, '#', index_name
        ) as ins 
      from log order by __time__ 
      limit 10000
      • query-04
      * 
      and index_name : load | 
      select 
        date_trunc('minute', __time__) as time, 
        region, 
        avg(value) as value 
      from log group by time, region order by time limit 1000

      通过query01我们可以得到对应的如下信息,这里表明我们我们当前的logstore中含有多少不同的曲线,每个曲线的标识是什么,为了更好的观察这1300多条线,我们可以使用流图来观察,一次性在一个图表中绘制出来对浏览器资源消耗也比较大,及时能绘制出来,也很难从这里得到有效的信息。我们使用query04来看下在少数曲线的情况下的可视化情况。在来对比下query03可视化的效果。
      image.png

      image.png

      image.png

      3.2 聚类实战

      通过上述的观察,我们能否将若干条曲线进行聚类呢,将较为相似的曲线划分到一类中,在进行可视化分析,进而打到降维分析的目的。使用如下SQL可以将N条线快速的聚类,在这里我们选择的指标是机器的负载(load)还是比较关注不同机器的水位的变化情况,因此我们可以选择ts_hierarchical_cluster这个函数,我们得到一张分面图,为了更加直观的可视化这个图表,您可以将其存储在Dashboard中更加直观。

      * 
      and index_name : load | 
      select 
        ts_hierarchical_cluster(time, value, ins) 
      from 
        (
          select 
            __time__ as time, 
            value, 
            concat(
              region, '#', machine, '#', index_name
            ) as ins 
          from 
            log
        )

      image.png

      3.2 相似性查找

      通过如下SQL,我们可以获得到跟 aysls-pub-cn-beijing-k8s#192.168.7.254:9100#load 这条指标曲线相似的机器的列表,并通过如下的流图进行可视化,观察我们的结果。在相似性函数中含有一些相似性判别的方法供大家选择:shape、manhattan、euclidean

      * 
      and index_name : load | 
      select 
        cast(
          cast(ts_value as double) as bigint
        ) as ts_value, 
        cast(ds_value as double) as ds_value, 
        name 
      from 
        (
          select 
            tt[1][1] as name, 
            tt[2] as ts, 
            tt[3] as ds 
          from 
            (
              select 
                ts_similar_instance(
                  time, value, ins, 'aysls-pub-cn-beijing-k8s#192.168.7.254:9100#load', 
                  10,
                  'euclidean'
                ) as res 
              from 
                (
                  select 
                    __time__ as time, 
                    value, 
                    concat(
                      region, '#', machine, '#', index_name
                    ) as ins 
                  from 
                    log
                )
            ), 
            unnest(res) as t(tt)
        ), 
        unnest(ts) as t(ts_value), 
        unnest(ds) as t(ds_value) 
      order by 
        ts_value 
      limit 
        10000

      image.png

      ]]>
      从Kafka实时同步数据到日志服务指南 Fri, 20 Jun 2025 02:20:34 +0800 背景

      Kafka 作为使用最广泛的消息队列之一,被非常普遍的应用于日志场景。通过机器上的采集客户端采集日志或者使用Producer直接写入,再供下游的数据处理系统如Spark,Flink等进行消费,是非常常见的大数据架构。

      阿里云日志服务底层存储是一个类似Kafka的数据系统,作为一个消息队列可以100%覆盖Kafka的功能。在此之上,作为阿里云上被广泛使用的日志分析产品,日志服务还提供了支持大规模数据集的实时查询分析能力,非常丰富的数据可视化报表,实时告警等功能。对于已经采集到Kafka中的数据,支持实时同步到日志服务中进行分析。

      111.png

      如何配置

      在开始之前需要先开通日志服务,并且创建好用于保存数据的Project 和Logstore。关于Project 和Logstore的介绍可以参考官方文档 :https://help.aliyun.com/document_detail/28961.html

      1 功能入口

      222.png

      2 选择Project与Logstore

      在开始同步之前,需要提前创建好用于存储数据的Project 和Logstore,如果需要对数据进行查询还需要提前对数据配置好索引。

      333.png

      3 输入Kafka 配置

      444.png

      4 调度方式

      日志服务通过Kafka 消费组流式消费数据,根据数据量自动水平扩展消费者个数。后台根据执行间隔定时调度同步任务,当同步任务从Kafka中超过5分钟没有消费到数据时当次任务结束,等待下一次调度。

      555.png

      查询日志

      日志从Kafka 同步到日志服务后,可以在控制台查询分析。

      666.png

      更多资料

      日志服务官方文档: https://help.aliyun.com/product/28958.html?spm=a2c4g.11186623.6.540.4af0bda9n7QANH
      日志服务查询语法简介:https://help.aliyun.com/document_detail/43772.html


      ]]>
      3亿人在用的钉钉背后,TA的作用功不可没 Fri, 20 Jun 2025 02:20:34 +0800 5-6-3.gif

      图数据库GDB的精彩详情

      在6月9日的“全速重构”2020阿里云·线上峰会中,阿里云智能数据库事业部的资深产品专家斗佛开启了全球首发4款云数据库新产品——云数据库专属集群、图数据库GDB、云数据库Cassandra版、云数据库ClickHouse。今天小编为大家带来图数据库GDB助力钉钉构建百亿量级知识图谱的案例。

      钉钉(DingTalk)是阿里巴巴集团专为中国企业打造的免费沟通和协同的多端平台,提供PC版,Web版和手机版,有考勤打卡、签到、审批、日志、公告、钉盘、钉邮等强大功能。钉钉因中国企业而生,帮助中国企业通过系统化的解决方案,全方位提升中国企业沟通和协同效率。

      借助阿里云图数据库GDB,钉钉可高效处理百亿量级人与人、人与企业、企业与企业间的复杂关联关系数据,并构建了工作场景下的知识图谱,具备深度关联关系的挖掘能力。在推荐加人,拉新促活等场景取得了良好效果。

      钉钉天然存在有人人、人企、企企间的复杂关系,形成庞大的用户、组织网络,使得基于网络的社区聚类、关系裂变传播等成为可能,进而用于大规模的拉新、用户促活、工作人脉沉淀等等。经过多年的建设,钉钉已经沉淀了各个数据域的数据资产,但仍存在如下问题:

      1)缺少统一的关系数据沉淀。例如需要查询一个人在多个组织下有多少个同事时,需要多次关联用户组织关系表才能获得。特别是在线推荐服务场景,需要做大量的关系扩散计算,分散的关系数据已经很难支撑起算法的进一步发展。因此需要构建一个统一的关系数据服务,提供离线和在线的数据查询及多度图扩散,而现有的传统关系型数据库已捉襟见肘。

      2)缺乏关系的深度认知和推断。目前钉钉的数仓建设主要围绕着客观数据,而在具体的业务场景中,往往需要对数据更深入地认知和推断。例如在推荐加人场景中,我们需要进行同企识别,推荐用户的潜在同事。

      钉钉团队经过多方调研论证,发现图数据库在处理海量关联关系数据方面具有查询效率高,模型直观,编程简便的独有优势,在知识图谱、社交推荐等领域有广泛应用,非常契合钉钉知识图谱应用场景。

      阿里云GDB是一款支持ACID事务,兼容Gremlin和Cyper两大主流查询语言,提供99.95%企业级高可用保障的全托管在线图数据库。并提供免费的专家服务,给客户提供建模指导和一对一的解决方案。
      234.png

      图数据库GDB支撑了钉钉百亿量级用户关系的存储和高效查询,为钉钉构建知识图谱,提供了深度关联关系的挖掘能力。在推荐加人、拉新促活等应用场景得到广泛应用:

      推荐加人。在钉钉上创建团队后,管理员需要从通讯录一个个选择,或者手动输入手机号码进行加入,加人效率低。如果能为用户推荐一些潜在同事,帮助其实现一键添加,那么用户组建团队就会更加方便。推荐加人属于职场社交推荐,我们基于DingTalk Graph实现了整个召回、排序链路。
      123.png
      (点击查看大图)

      拉新促活。邀请好友是App产品常用的一种拉新、促活方式。通过激励用户邀请好友、创建组织,可以为钉钉带来新用户、曝光度、留存率、活跃度,甚至形成裂变。我们基于DingTalk Graph构建了一个邀请能力模型(Invitation ability Model)来建模一个人在其工作社交网络中的邀请能力。

      通过阿里云图数据库GDB实现了百亿点边存储和ms级查询能力,针对图结构数据的存取特点,在图计算层和图存储层均进行了高度优化,满足钉钉百亿量级点边海量数据的多度关系毫秒级查询需求。服务的高可用,99.95%企业级SLA保障:GDB采用主备高可用架构,提供99.95%企业级SLA保障。

      当主节点故障时,系统自动切换,保障钉钉的业务连续性。易用和易运维,云服务全托管,提供备份恢复、自动升级、监控告警、实例升降配等丰富功能,大幅降低钉钉使用和维护图数据库的难度,使钉钉可以专注于应用本身的开发。

      如今,钉钉的注册用户数已突破3亿人,成长为中国领先的智能移动办公平台。而拉新促活功能为钉钉用户数增长起到了很好的促进作用,推荐加人功能使企业在创建团队钉群场景变得简单高效。面向未来,钉钉期望与阿里云图数据库GDB共创,构建千亿量级关系数据的高效处理能力,在智能推荐、社交网络等场景为用户提供更为丰富的功能。

      太阳伞免费领取活动已于今日18:00结束,我们将统一在下周三为满足活动要求的同学寄出,请同学们耐心等待哟~

      点击文字
      解锁图数据库GDB的更多精彩详情
      还有新用户首购9.9元包3月的限时优惠哦!
      图片.gif

      ]]>
      新基建风口下,为什么我们必须冷静思考 Fri, 20 Jun 2025 02:20:34 +0800 今年有个备考任务,加上近期工作有点忙,春节假期没结束就开始在家办公了。疫情期间一直坚持每天稳定输出1~2篇疫情防控专题信息,在政府和企业之间发挥上传下达的信息传递作用,发挥下应有的节点作用。所以近期没有及时收听晓夜校的课程。在群里看到大家畅聊新基建才又去补了补课。不然就真是躲在小楼成一统,两耳不闻窗外事了。

      听校长讲新基建,又上百度搜了一下,发现这个新基建其实已不是一个新概念,2018年的时候政策就已经有提出,之所以现在又引起大家的重视,是因为3月4号政治局会议高层提出要加强新基建进度,所以才又占据各大网页头条,吸引了大家的注意力。

      2018年年末的经济工作会议上明确提出:基建的重心不再是房地产,而是特高压、城际交通、物流、市政基础设施,以及5G、人工智能、工业互联网等新型基础设施建设。2月14日中央全面深化改革委员会第12次会议,指出要进一步打造集约高效,经济适用,智能绿色安全可靠的现代化基础设施体系。本月的政治局常务会又为新基建按下了快进键。可见国家对新基建的部署是循序渐进的。每年的工作报告倒是都看,说实话没有引起特别的重视,可能是身在此山中吧。

      一直以来,说起基建,大多数人的第一反应都是钢筋、水泥、房地产这些传统基建,而经济工作会议上提出的新型基础设施建设和以往的传统基建不一样,被称为“新基建”。新基建会带来什么?会是革命性的变化吗?校长已经讲了很多变革和未来它将带来的影响,受益匪浅,特别是对我们一些有着山洞思维和孤岛思维的企业,更是需要重塑网络,创造新的优势。然而,当我们在关心变化的时候,也许我们还可以试着从反面想想有什么会是不变的。

      01

      国家发展的趋势,城市化建设不会变

      不管是新基建还是老基建,着力点都是在城市建设和发展上。这些年我省的城市建设一直没有停下脚步,包括新基建的战略布局。近些年各地根据产业基础和周边产业情况,积极引进项目布局电子信息相关产业,初步形成了“一核两带多点”的产业发展格局。一核是郑州核心发展区,两带是以鹤壁,新乡,许昌,信阳等为主要节点的京广线产业带,和以三门峡、洛阳、商丘为主要节点的陇海线产业带,多点是围绕智能终端,智能传感器,光电子,电子材料,锂电子电池等产业,形成了多个特色产业园区。去年还引进了华为的笔记本电脑生产项目,打造鲲鹏计算产业链和生态链。2019年全省规模以上电子信息制造业增加值增速11.4%,从营收规模上看,在全国位列第8,这些都是城市建设的新基建。

      物流上我们有一定的地域优势和设施基础。但是现在货运物流我们还存在有短板。我们国家整体物流成本比较高,占GDP的15%多,在全球算是最高的,美国和欧洲的物流成本占GDP只有7%,咱比人家高一倍,原因是我们铁路用的太少。我们国家货运总量,铁路只占5~6%,公路占80%多,然后轮船、航运、内河水运加起来10%多。我们有着强大的铁路网络,是重要的铁路枢纽,但货运的铁路利用率却相当低,还不如美国几十年前建设的铁路货运率高。水运也没有完全利用起来。铁路的运输成本是公路的1/5,而水运是铁路的1/4,这些都没有好好利用。空运成本最高,是针对高附加值产品的。再一个我们缺少大型的物流企业,综合类的物流企业少,包括冷链物流、专业物流更少。所以我们现在正在着力培育物资集团,打造一个综合的物流企业。

      02

      传统制造业不会被淘汰,但转型升级的发展方向不会变

      传统产业不等于落后产业,新基建、新产业,战略性产业也不等于都先进。卢森堡这样一个以金融服务著称的小国家,还保留着千万吨的钢铁生产能力。光伏、多晶硅是新能源、新材料,包括新能源汽车,发展多了也会是过剩产能。要构成产业链,形成产业生态集群,必须有差异化的生产链条。转型升级、高端化是发展趋势和要求。

      河南的产业门类还是相对比较齐全的。以汽车为例,全省现在汽车整车企业超过14家,改装车突破100家,摩托车企将近30家。去年全省汽车工业增加值增速为6.9%,占工业比重的5%,汽车整车产销超过76万辆,处于行业发展平均水平。宇通在国内市场占有率达35%,全球市场占有率连续8年保持第一。有整机就一定要有相应的配件。如果配件低端将不足以满足产品的高端化,这些年我省正在建设汽车配件体系,转向器,减震器,传动轴、水泵,气缸套、凸轮轴、线束、动力电池等10多种零部件产品聚集明显,产量位居全国前列,并成为众多跨国公司的供应商。政府的作用就是组织企业形成产业集群,形成产业链,形成产业的优势。

      工业革命发展到现在,由1.0的机械化,蒸汽机,到2.0的电力化,发明了电,到3.0的标准化、批量化,有了计算机,现在到了4.0时代的智能化,前三化都是提高效率的,都是降低成本,满足供应大批量生产流水线作业。三次工业革命把人类社会的生产力发展到了一个高阶段。中间还伴随着交通革命,有了汽车,火车,有了高速、飞机、宇宙航空。伴随着通讯革命,都使提高效率的速度越来越快,通信能力越来越强。我们的郑州跨境电子商务受到过总书记和总理的表扬,这其中有三大技术性能,一秒数据收集一秒结算一秒通关,这些都是靠网络的,减少交易成本。

      郑州的网络干线,总带宽达到730G(18年的数据),是国家骨干互联网直联点,在全国13个国家级互联网骨干直联点中位居第二,这是互联网的枢纽干线,第2个层次的基础设施才是我们讲的大数据、云计算、移动互联网。这些都极大增强了郑州信息通信枢纽地位和信息集散能力。

      智能化的变革,是能既保留大批量流水线作业的高效率、低成本,又能满足了人们多种需要的个性化。这是它有别于前三次工业革命的核心。但智能化发展一定是建立在工业自动化基础之上的,没有传统产业做支撑,智能化也会成为新吹的泡沫。就像美国股市的崩盘,我不大了解股市,但从美国制造业转移、产业空心化就能想像的出,一个不创造实际价值的产业体系,或者说实体产业创造价值的速度不及金融催生债务资产的速度,崩盘应该也是早晚的事。

      03

      固本培元、强身健体的思路不能变

      如果说新基建建设中的5G、物流、网络,这些集约高效、经济适用、智能绿色、安全可靠的现代化基础设施体系是硬件,是“算力”的基础,是为企业强身健体提供了一个高效安全、可选择性强的健身环境。那么区块链、云计算、人工智能则是从“算法”角度,给了企业一个固本培元、华丽转身的高超技能。各地在新基建上的投资规模据说已经高达30多万亿,这样的大手笔不是一次性漫灌式的,而是一个长远重效的投入。一定意义上是在这个疫情寒冬中,为中国经济恢复吹起的一缕春风,更是对未来发展未雨绸缪铺下的一条路。

      企业当下最清醒的战略,最重要的战略就是在寒冬中要活下来,苦撑不下牌桌,看清大势,学习在市场中寻找机遇。我们一国企原本是生产汽车帘子布的,疫情发生后,口罩短缺,主要是原材料熔喷布价格暴涨,从2万涨到40-50万每吨。企业发现商机,果断上马生产线,政府也很支持,极速审批,3月初部分产品已送相关企业使用检验,预计很快投放市场。

      我们要做的,应该也是在大势中寻找自己的战略重心,及时有效调整动作,无中生有,立于不败。

      另,武汉的问题,我觉着他们最缺一场深刻的反思,总结经验教训,为什么疫情会发生在武汉,为什么它蔓延的速度会如此之快,在疫情发生后组织效率上存在的问题,以及后期在处理问题过程中总结出的好经验,可以写本书,就叫《失控的城市》。

      再者,武汉民生与经济发展缺少的是资金,这是实实在在的,国家应该出台此定向政策,减免税和扶持资金方面,要普惠政策力度再大些,这个我们力所不能及,但党员捐款里有我一份心意的,哈。

                                晓夜校.水木悠寻
      ]]>
      AI概述:阿里文娱智能算法的新应用 Fri, 20 Jun 2025 02:20:34 +0800 作者| 阿里文娱资深算法专家 胡尧

      **一、文娱消费新体验-Free Viewpoint Video
      **

      001.jpg
      面向文娱消费新体验,文娱算法团队基于整体的视频生产、播放、交互式体验等环节做了非常多的技术探索,在视频子弹时间的基础上进一步延展,延伸成更加经济通用的Free Viewpoint Video技术,构建完善的现场-云-边-端的技术链路。

      今年优酷与CBA达成全方位的合作,在新赛季首次落地互动FVV体验,变革传统体育赛事的观看体验。我们还主导建设FVV视频技术国家标准,同时承担了国家“科技冬奥”“冰雪项目交互式多维度观赛体验技术与系统”项目,让更多普通用户享受到新一代观看体验。

      二、视频消费商业化新模式-创意广告

      在视频消费商业化新模式上,重点在创意广告领域,打造动效酷炫、更原生、可互动的广告形态,通过规模化实现中长尾内容的变现。

      具体是构建了综合的三维环境感知和理解算法,在多场景多模态的视频内容理解、云端结合的CG 渲染引擎等领域都有深度探索,并且构建云端结合的c c级渲染引擎,实现AI两大平台(视频打点平台、视频特效生产平台)、一个中心的战略(创意广告制作中心)。

      **
      三、视频创作新平台-基础素材自动化生产与检索**
      002.jpg

      在视频理解领域的另一个重要应用,就是搭建了视频创作新平台。

      首先,我们实现了基础素材的自动化生产与搜索,将传统的静态人物动作和场景实现升级到shot 级别,支持动态素材的检测与识别。

      同时,实现精细化抠图,包括对动态人物和物体场景的提取,使这些素材可以被创作者更快速的应用到新的创作环境中。通过交互式的精细化分割算法,动态地将经典视频中的素材提取出来,实现简易化绿幕的效果。

      当大量的素材被生产出来,我们同时提供基于准素材级别的智能化检索系统,用户只需要通过语义的文本或语音输入,就能实现对整个素材库的检索。例如用户搜索“吴倩拥抱”,系统就会呈现出整个《冰糖炖雪梨》中有吴倩拥抱的场景。

      **
      四、视频创作新平台-封面图自动化生产**

      003.png
      另外,我们实现了封面图自动化生产。基于主要人物、场景、美学评级、元素多样性等方面生成不同维度的封面图,并提供智能裁剪服务,满足16:9、4:3或者3:4等各种场景需求。同时在某些场景中实现动图的自动化生产,即实现千人千面的内容+素材的统一个性化推荐,助力运营分发的提效。

      五、视频创作新平台-模板式视频半自动化生产

      鉴于优酷有海量的IP版权内容,我们研发了一系列的剪辑合成技术,自动对视频的故事线、内容模板进行提取,并在此基础上在海量视频中进行智能化的二次创作,实现如节目卡点剪辑、Video Highlight & Summary技术生成的前情提要等产品。同时具备视频的形态转换技术,将横版的视频通过AI算法,识别显著性主体区域并进行美学评判,实现竖版视频的自动化生产。

      这些技术能够有效的为商业化提供更多素材,同时为B端提供更多能力。
      004.jpg

      在这个基础上,我们才能实现基于元素级的视频深度理解技术,我们将传统的基于用户行为的内容分发体系和基于视频内容理解的视频内容分发体系进行了有效结合,实现了群体智慧和计算机视觉在美学和AI上的融合,实现了从整个封面图内容的原数据分析,到整个用户行为偏好的判断,实现千人千面的内容加素材的个性化推荐,有效提升整个业务场的分发效率。

      ]]>
      Gartner 2020的十大数据分析趋势 Fri, 20 Jun 2025 02:20:34 +0800 前言:更多关于数智化转型、数据中台内容可扫码加群一起探讨
      668d7f5941782665ed1f41529db3eb677f4b9379.png
      阿里云数据中台官网 https://dp.alibaba.com/index


      来源:搜狐新闻

      image.png
      这些数据和分析技术趋势将在未来三到五年内帮助加速更新,促进创新和重建社会。 数据和分析领导者必须研究如何利用这些趋势,并进行"必备"投资,以实现重置后的恢复和重新发明。

      趋势1:更智能,更快,更负责任的AI

      到2024年底,将有75%的企业从人工智能试点转向运营,流数据和分析基础架构的规模将增加五倍。

      在当前盛行的环境中,诸如机器学习,优化和自然语言处理之类的人工智能技术正在为病毒传播以及对策的有效性和影响提供重要的见识和预测。

      其他更智能的AI技术,例如强化学习和分布式学习,正在创建更具适应性和灵活性的系统来处理复杂的业务。 例如,基于代理的系统可以对复杂的系统进行建模和升级。

      追究AI责任和模型透明度对于防止错误决策至关重要

      在新芯片架构(例如可以部署在边缘设备上的神经形态硬件)上的大量投资正在加速AI,ML计算和工作负载,并减少对高带宽集中式系统的依赖。 最终,这可能会导致具有更高业务影响的更灵活的AI解决方案。

      让AI负责并保持模型透明对于防止错误决策至关重要。 它将促进更好的人机协作和信任,以便整个组织可以更好地采用和调整决策。

      趋势二:仪表盘使用的下降

      具有更自动化和消费者体验的动态数据应用程序将取代可视化,点击创建和探索。 结果,用户将减少使用预定义仪表板的时间。 转向上下文数据应用程序意味着最相关的见解将基于上下文,角色或目的传递给每个用户。 这些动态洞察力利用诸如增强分析,NLP,流量异常检测和协作之类的技术。

      数据和分析主管需要定期评估他们现有的分析和商业智能(BI)工具。 初创公司提供预定义的仪表板以外的新增强功能和NLP驱动的用户体验。

      趋势3:明智的决策

      到2023年,超过30%的大型组织将使分析师从事智能决策,包括决策建模。 决策智能集成了多个学科,包括决策管理和决策支持。 它包含了复杂自适应系统领域中的应用程序,将各种传统和高级学科结合在一起。

      它提供了一个框架,可帮助数据和分析领导者设计,建模,匹配,执行,监视和优化业务结果与行为之间关系中的决策模型和流程。

      当决策需要各种逻辑和数学时,有必要进行自动化,或者至少进行记录和审计,以探索决策管理和建模技术的使用。

      趋势4:X分析

      Gartner创造了术语" X分析",其中X是具有不同结构化和非结构化内容(例如,文本分析,视频分析,音频分析等)的一系列数据变量。

      数据和分析负责人使用X分析来解决社会上最困难的挑战,包括气候变化,疾病预防和野生动植物保护。

      在疫情爆发期间,人工智能在整理大量研究论文,新闻来源,社交媒体帖子和临床试验数据方面发挥了关键作用,并帮助医学和公共卫生专家预测疾病的传播,规划能力,寻找新疗法, 并找出弱点群体。 X分析与AI和其他技术(例如图表分析)(另一个热门趋势)相结合,将在识别,预测和计划未来的自然灾害和其他危机中发挥关键作用。

      数据和分析主管应探索现有供应商提供的X分析功能,例如用于图像,视频和语音分析的云计算供应商,但也应认识到创新很可能来自小型初创公司和云计算供应商。

      趋势5:增强的数据管理:元数据是"新的黑马"

      增强的数据管理使用ML和AI技术来优化和改善操作。 还将用于审核,继承和报告的元数据转换为支持动态系统的元数据。

      增强型数据管理产品可以检查大量操作数据样本,包括实际查询,性能数据和模式。 使用现有情况和工作负载数据,增强的引擎可以优化操作,配置,安全性和性能。

      数据和分析主管应寻求增强的数据管理,支持活动元数据以简化和集成其体系结构,并提高冗余数据管理任务的自动化程度。

      趋势六:云是礼物

      到2022年,公共云服务将在90%的数据和分析创新中扮演关键角色。

      随着数据和分析迁移到云中,数据和分析领导者仍在努力使正确的服务与用例保持一致,这将导致不必要的治理和集成开销。

      数据和分析问题已经从给定服务的成本转向如何满足工作负载的性能要求,而不仅仅是价格表。

      数据和分析领导者需要优先考虑可以利用云计算功能的工作负载,并在迁移到云计算时专注于成本优化。

      趋势7:数据与分析之间的冲突

      传统上,数据和分析功能被认为是单独的实体,并且需要单独管理。 通过增强的分析提供端到端工作流的供应商模糊了这两个市场之间的区别。

      数据和分析的这种冲突将增加独立数据和分析角色之间的交互和协作。 这不仅会影响提供的技术和功能,还会影响支持和使用它们的人员和流程。 角色范围将从IT中的传统数据和分析角色扩展到信息浏览器,消费者和公民开发人员。

      为了将冲突转化为建设性的整合,可以将数据和分析工具及功能合并到分析堆栈中。 除工具外,还应关注人员和流程以促进沟通和协作。 使用数据并分析生态系统和增强方法有可能提供一致的堆栈。

      趋势八:数据市场和数据交换

      到2022年,将有35%的大型机构通过正式的在线数据市场成为数据的卖方或买方,而2020年为25%。

      数据市场和交易所提供了一个集成第三方数据产品的平台。 这些市场和交易中心提供集中的可用性和访问权限(例如X分析和其他独特的数据集),从而形成规模经济,可以降低第三方数据的成本。

      为了通过数据市场货币化数据资产,数据和分析的领导者应通过定义生态系统合作伙伴可以依赖的数据治理原则,建立一种公平透明的方法。

      趋势9:数据分析中的区块链

      区块链技术解决了数据和分析中的两个挑战。 首先,区块链提供资产和交易的完整继承。 其次,区块链为复杂的参与者网络提供了透明度。

      除了比特币和智能合约的有限示例外,分类数据库管理系统(DBMS)将为审计单个公司的数据源提供更有吸引力的选择。 Gartner估计,到2021年,保密的DBMS产品将取代目前使用的大多数区块链。

      数据和分析应通过强调数据管理基础架构与区块链技术功能之间的不匹配,将区块链技术定位为对现有数据管理基础架构的补充。

      趋势十:从数据分析基础与价值之间的关系

      到2023年,图像技术将促进全球30%的组织快速进行情境决策。 图像分析是一组分析技术,可用于探索相关实体(例如组织,人员和事务)之间的关系。

      它可以帮助数据和分析领导者发现数据中的未知关系,并查看传统分析中难以分析的数据。

      例如,当世界对当前和未来的疫情做出反应时,图像技术可以分析来自人们手机上的地理空间数据的照片,以面对人脸识别系统,以确定谁可能联系了被冠状病毒检测呈阳性的人。

      考虑研究图形算法和技术如何改善AI和ML计划

      与ML算法结合使用时,这些技术可用于梳理成千上万的数据源和文档,从而帮助医学和公共卫生专家快速发现可能对某些患者产生更多负面影响的新疗法或新因素。

      数据和分析主管需要评估将图形分析集成到分析产品组合和应用程序中的机会,以发现隐藏的模式和关系。 此外,请考虑图形算法和技术如何改善AI和ML计划。

      (本文翻译自Sajjad Hussain的文章《Top 10 data analysis trends for Gartner 2020》,参考:
      https://medium.com/dataseries/top-10-data-analysis-trends-for-gartner-2020-c683a74e99a2)


      数据中台是企业数智化的新基建,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。目前正通过阿里云数据中台解决方案对外输出,包括零售金融互联网政务等领域,其中核心产品有:

      官方站点:
      数据中台官网 https://dp.alibaba.com
      数据中台钉钉群二维码2.jpg


      ]]>
      用竖屏看热剧!阿里文娱视频横转竖技术实践 Fri, 20 Jun 2025 02:20:34 +0800 作者:阿里文娱算法专家 闵公

      常见的机器视觉问题,诸如目标检测、主体标定、目标追踪、视频增强等作为独立技术问题来求解,是不是有些枯燥?在文娱产业中,如何将这些视觉技术进行创新和组合形成完整技术栈,对海量横屏播放的影视剧和短视频自动转换成竖版播放的视频?
      且看阿里文娱摩酷实验室的算法专家闵公在GMIC Live 2020智慧文娱技术专场中的分享,主要介绍如何“基于机器视觉算法自动化”将海量横版长剧集转换竖版视频,包括横版视频的主体自动选择算法,镜头平滑能力等,希望对大家在视觉算法如何运用在文娱行业中有所启发。

      核心技术内容包括:
      1) 视频横转竖技术链路搭建
      2) 复杂环境下主体自动标定模型
      3) shot镜头平滑和标定追踪交互机制
      4) 视频裁剪导致降质条件下的画面恢复

      一、横屏转竖屏的视频裁剪的行业需求

      首先,站在海量内容消费者的角度来看,90%以上的视频内容消费者会选择单手竖持手机,同时也有50%以上的用户会选择将屏幕进行竖向的锁定浏览。同时视频内容消费者倾向于将视觉聚焦在焦点主体内容,而不是背景上。
      image.png

      其次,站在内容生产侧,大剧制作有95%以上是横屏拍摄,站在PGC短内容的制作上,像优酷全娱乐、体育等,多是以横屏内容制作为主。如果通过人工将横版拍摄的视频进行剪辑转竖,效率低,效果不可控。

      二.横屏转竖屏的产品落地化进程

      优酷人工智能平台推出自研的视频自动化的横屏转竖屏技术,应用于视频二次生产和智能封面图生成业务中,目前该技术已经覆盖优酷的OGC剪辑,海量UPGC竖版短小视频生产,智能封面图生产,同时输出给阿里云,服务于文娱企业客户。
      image.png

      三.横屏转竖屏的视频技术链路

      智能裁剪技术主要应用于以多人或者单人为主体的影视剧场景,我们将目标检测,跟踪,识别等技术进行创新和结合,开发了完整的视频智能裁剪技术链路,面对实际业务中的主体标定,视频帧间抖动,视频黑边填充等问题针对性的研发了算法解决方案,可以根据不同的业务场景将各算法可插拔的配置进主裁剪pipeline中,阿里文娱视频智能裁剪技术的研发给内容行业的素材自动化制作,剪辑作品的视觉效果和制作成本降低等方面都带来了大幅度的提升。

      在视频智能裁剪技术链路中,我们研发了前处理模块(包含镜头切分, 画面尺寸判定,黑边检测裁剪等),主体选择模块,主体追踪模块和后处理模块(包含画质增强,字幕/logo检测,画面内容修补等),下面分别介绍四个模块。
      image.png

      四.视频裁剪核心研发模块

      一)前处理模块:
      前处理模块包括分镜边界检测模型,画面尺寸判定算法,黑边检测与剪裁算法等三个模块,其中分镜边界检测模型根据视频画面将视频分成多个镜头片段,针对渐变过渡的镜头,采用视频帧的表征向量逐帧计算相似度进行精细切分;
      image.png

      画面尺寸判定算法使得裁剪可以在不同的画面尺寸中进行自动选择,包括(宽:高)16:9, 4:3, 1:1, 3:4, 9:16等任意尺寸,通过对视频帧进行抽样后根据目标的显著性和运动特性计算得出显著区域的大小进行剪裁尺寸适配;
      由于大量upgc横版和竖版视频存在上下黑边填充现象,但上下黑边在自动裁剪后会严重影响用户体验。因此我们使用霍夫变换和直线分类来解决黑边检测与剪裁的问题,并且根据剪裁后的不同尺寸自适应的选择三层重叠样式或者1:1加包框样式进行视频再生产;
      image.png

      二)主体标定模块
      我们根据人工标注的影剧综主体GT数据,设计了主体自动选择模型对视频帧中的主体进行自动标定。主体自动选择模型将视频中的人脸,人体bbox,显著性区域,图像清晰度等候选区域进行roi align对齐后,通过深度卷积网络进行最佳主体选择模型的训练,通过和显著性模型以及注视预测模型进行max-IOU指标对比,我们提出的主体自动选择模型表现SOTA。
      image.png

      同时我们将主体选择模型应用于复杂环境下的场景(如动物世界,大型晚会,新闻联播等)下进行效果测试,裁剪后的竖版视频效果符合预期,从而验证了我们提出的主体选择模型具备的泛化能力。

      在主体数据标注的过程中,我们制定了一套主体选择标注标准,包括主体中心化,主体max尺寸、主体尺寸比例,主体的姿态以及主体稳定性等。完成了主体图像数据集共9.5k的标注,视频数据集125个视频,共13.2万帧的标注。针对视频帧存在的多主体和人工标注的抖动问题,我们引入了reid和平滑滤波来为辅助解决上述两个问题。
      image.png

      三)主体追踪模块
      主体追踪模块包括目标追踪算法,镜头平滑算法,主体标定和主体追踪交互机制。通过对多个物体运行多次SOT追踪得到关键帧后续相邻帧中主体目标对应的位置,形成连续视频帧的镜头标定结果。我们在追踪模块中引入backward tracking策略,将短时track能力扩展为长时跟踪,并进行了local-to-global search based tracking,以此来降低追踪模块和主体标定模型的交互次数和计算时间。同时针对主体切分比例采取了黄金分割比例来提升美学观感。
      image.png

      由于目标追踪算法得到的镜头剪裁位置并不是平滑渐变的,这导致画面抖动,引起用户观看眩晕等较差体验,因此通过时间序列离群点检测和Kalman filter等技术,将异常定位点t进行平滑,解决了裁剪后视频帧间抖动问题,抖动幅度Jitter Degree得到了显著性的降低,人工评估视频帧后观感流畅。同时通过主体标定和主体追踪交互机制,保证了主体目标在镜头切换情况下的镜头内容连续性。

      image.png

      四)后处理模块
      针对视频剪裁后的视频画质问题,我们开发了后处理模块(包含画质增强,字幕/logo检测,画面内容修补等),主要解决剪裁边界可能的logo/字幕截断问题和裁剪后主体相对放大和编码导致的分辨率降低问题。其中我们针对性的设计了去噪、超分辨率模型,对裁剪后的降质视频进行画质提升,在超分模型研发中,我们在训练数据增强上采用自适应采样算法(如下图所示,红色bbox由随机采样得到,绿色bbox由自适应采样得到)使得采样得到的图像patch集中在纹理细节丰富的区域,在模型设计上,采用了multi-term loss
      和multi-branch module的结构进行模型训练,最终超分模型在技术指标psnr和人工背对背打分上都得到了显著提升。
      image.png

      结束语

      视频智能裁剪技术生产的视频和封面图广泛应用于优酷的各个场景,并得到了业务方和阿里云客户的一致认可,我们对视频智能裁剪算法栈进行了整体性能优化,达到处理时间仅1:2视频时长,目前该技术累计对优酷综艺:演技派,这就是街舞,这就是灌篮;优酷剧集:陆战之王,天雷一部之春花秋月,微微一笑很倾城等百部OGC进行裁剪服务,裁剪后的竖版视频用于抖音,微博等外渠宣发和站内投放,同时主体标定算法服务于搜索双列封面图生产,镜头平滑算法服务于弹幕人脸项目,视频裁剪算法已经部署在阿里云上,由于目前行业内竞品尚无成熟技术方案,已经通过申报《基于主体目标标定与追踪的视频智能剪裁技术》,《基于智能画面分析和多层级主体目标标定的图像智能剪裁技术》专利的方式来保障该产品技术的竞争优势,期待阿里文娱视频裁剪技术为中国的视频娱乐行业创造更大价值。同时感谢AZFT计算机视觉与分析实验室的朱建科老师在项目过程中的技术指导和大力支持。

      ]]>
      云端研发新基建:Serverless 与持续架构服务落地实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800

      作者 | 风驰

      image.png
      距离上一篇文章《我心中的云时代原生开发环境》的发布,已经过去了 4 个月的时间,在《我心中的云时代原生开发环境》这篇文章中,我们探讨过云厂商的愿景,云计算的趋势与现状以及研发团队的架构服务诉求等背景。今天,我想结合我和我的团队在这 4 个月时间内一起设计打造的云开发平台(Cloud Workbench)跟大家进一步聊聊,如何打造全云端研发的新基建,去服务好研发团队和雇主。

      云时代创新核心要素

      首先,让我们快速将视野放大到社会商业爆炸式增长的云时代,无论是创业公司还是发展中的公司,都希望能有一个低成本、可持续支撑的架构服务,帮助自己的业务持续发展,用户流量从小到大,无需变更架构,更不用中断业务。

      这种架构服务诉求背后的核心痛点体现在业务快速试错与流量快速增长之间的矛盾。如果从传统的架构方式去思考,这个问题很难调和:如果要快速奔跑,就没有时间好好思考设计架构;如果架构设计不好,就无法支撑未来巨大的流量;而如果花时间把架构设计好再动手,就没办法快速奔跑,很可能错过一个商业创新的时间窗口。另外,还有一个未知的疑问,这个设计好的架构真的够好么?

      结合我们之前的探索实践,我们知道,借助云原生 Serverless 的能力:实时弹性、按量付费,正好可以帮助我们把上述问题提升到一个新的维度去解决:业务完全可以放飞自我快速奔跑,架构服务由云原生Serverless矩阵来提供,保证流量再大也不怕。

      中小研发生态现状

      基于上述的一个判断,我们认为,现代商业社会的启动过程:从一个 idea 的诞生,到快速试错,再到上线服务用户的过程,有了一个很好的方案去支撑。但是,就像布道师们经常讲的一句话,人人都在谈云原生 Serverless,实际上并不是人人都知道怎么落地 Serverless。我们不妨来看几个真实的创业公司案例。

      案例一,天猫精灵技能业务

      天猫精灵的技能应用开发本身与天猫精灵开发者平台之前的连接较为松散,技能应用的开发对于一个中小开发者而言,启动成本较高。开发技能应用过程中的技术栈和方案也因人而异,因团队而异,也由于广大的开发者对技能应用背后的大流量没有一个一致的高水位保障,使得天猫精灵平台在做推广的时候也经常遇到阻力,担心在推广之后,很多技能无法承载大量涌入的活动流量,反而影响活动效果;

      案例二,某直播互动健身创业公司

      Y 公司是一家面对面直播互动健身的创业公司,研发团队的构成有 2 个前端、2 个后端、1 个架构师、1 个 iOS、1 个产品经理、1 个设计师,产品的构成有微信小程序、iOS APP、Android APP、PC 端 WEB 应用;

      当前的核心痛点及诉求:

      1. 研发成本、架构人员的浪费;(这已经算是幸福的烦恼了,很多公司是找不到合格的架构师的)
      2. 新人落地的成本(每个新人都有 3 个月的熟悉环境、流程、业务的成本);
      3. 自研自建业务架构的成本,要等业务架构确定后才能动手;
      4. 业务线切换的沉默成本比较高,如新业务启动到上线:技术架构的选型、服务的复用等等都是损耗;
      5. 运维成本,如:承接推活动来的高峰流量以及平时流量的平均成本;

      案例三,某软件外包服务商

      O 公司是一家 base 杭州的软件外包服务商,他们的研发团队构成为:5 个 Java、4 个前端、1 个iOS、1 个Android、1 个产品经理、2 个测试、5 个商务、6 个品牌、2 个 UI 设计;

      当前的核心痛点及诉求:

      1. 10人以下规模来什么做什么,没有沉淀,没有办法复用,没有高的盈利回报,人都铺在业务上,没时间学习架构,进入了一个恶性循环,需要一个很低的成本去采用一个先进的技术架构方案,确保不落伍,同时可以继续聚焦业务开发;有了可复用的空间(如架构、组件、服务)才有盈利的空间。10人以下的外包公司,CTO是不太可能去招的,40万一年的话,公司一半的利润就没了;
      2. 想依托于云,不过每家云厂商都产品众多,围绕自己的业务怎么知道有哪些产品适合,要一个个去挑选、学习,整体成本太高;

      核心要解决的问题以及产品化思考

      我们将上述调研的客户反馈诉求进行梳理,可以归纳出以下几点诉求:

      1. 人员、业务尽可能做到快速启动,低成本启动。开发人员能够快速进入业务开发,架构师能省就省,业务能够基于行业现有解决方案、基本业务架构、业务模块尽快启动;
      2. 开发人员的时间尽可能投入到业务开发中,但同时要保证业务所用技术架构的先进性:一个人的时间是恒定的,如何帮助中小企业把人员投入业务的时间从60%提高到99%,同时还能确保业务背后所用的技术栈及技术架构是行业内广受认可的;
      3. 线上业务能够做到按量付费:1、业务的流量高峰不会成为业务增长的瓶颈;2、类似于外包服务商/ISV,可以为他们的客户灵活制定弹性的服务体系;

      基于以上三点,我们进一步抽象用户群体以及场景和服务策略:

      主要用户群体

      1. 中小体量研发团队及创业公司研发团队;
      2. 要做开发生态的业务或平台;
      3. 行业软件/解决方案ISV/服务商;

      场景和服务策略

      1. 在快速商业化试错的创新创业场景下,通过集成设计以阿里云 Serverless 产品线为矩阵的业务架构,帮助用户快速迭代业务,同时保证业务上线后无需变更架构就可以持续支撑不断增长的流量,确保业务不中断,提高试错效率,降低试错成本;
      2. 在研发人员需要支持多业务线切换调度的场景下,通过集成云效研发协同底座的能力构建在线研发团队,通过设计解决方案实例化的能力构建统一应用开发环境,降低开发者在业务切换中的沉默成本,让开发者可以快速且专注地进入业务逻辑的开发,提高研发效率;
      3. 在需要快速启动业务的场景下,通过构建三套业务环境,帮助用户实现环境在线,降低环境准备的时间成本与投入成本;
      4. 为研发团队提供一种将应用开发方法和结果抽象成标准的格式化的解决方案的能力,用该解决方案统一快速地教育开发者;

      定义了用户群体、问题、场景以及服务策略之后,我们开始尝试去定义这个产品:

      我们要去打造一个全云端研发工作的平台,以业务、研发任务为用户界面,用户对云产品的感知尽量保持并限制在必要的情况之下,但是平台要在背后为他们提供一系列先进的云原生Serverless架构服务,同时,具备让用户的团队、环境、代码、协同等等实现在线的能力,帮助目标用户群体省钱、省时、可持续发展。

      核心技术方案

      作为一个面向用户业务视角而非云产品或任何单项研发能力视角的设计,背后就必不可少的要跟非常多的系统、产品、能力进行集成;并且,要达成与用户业务视角的关联,又需要额外设计一套核心应用模型去支撑。这里,我们通过两个架构设计,向大家阐述我们的实现思路与方式。

      系统集成架构

      1. 认同在线协同是大趋势。我们把自己定义为大协同领域的一环,最好能够依托于一个更具全局性的团队在线协同底座去建设,我们与云效合作共建,基于一个共同的团队模型以及数据去设计实现不同的领域能力。
      2. 原子研发能力分布广泛,以代码为主线进行串联设计,在开发者用户路径中,我们与 Codeup、Flow 等产品进行集成,与经济体共建 IDE 集成,共同推出 CloudIDE,为开发者提供从云上代码托管到云上研发,再到云上 CICD 的一站式服务;

      解决方案实例化架构

      用技术语言来描述的话,可以把解决方案实例化架构核心要解决的问题理解成将一个行业应用的开发经验进行 “序列化” 与 “反序列化” 的过程。

      我们与 OAM 团队合作,以 OAM 为规范,对构成一个行业应用的研发环境以及依赖资源进行格式化、规范化的描述,生成一个云开发平台所能理解和认识的解决方案,这是“序列化”的过程;

      当云开发平台去解析一个解决方案,核心会做两件事情,一是分析和生成一个基于阿里云产品矩阵构成的云原生 Serverless 架构,另一件事情是将依赖的资源做打包上传等预处理;然后开始生成任务,逐一进行生产、创建、安装,直至一个行业应用被初始化完成,这是“反序列化”的过程;

      通过这种设计,我们实现了云开发平台最为核心的能力:

      1. 帮助行业开发生态格式化、规范化地沉淀经验;
      2. 帮助行业开发生态快速分发、复制行业应用开发经验;
      3. 帮助行业开发生态无缝升级至先进的云原生Serverless架构;

      阿里云云开发平台正式上线服务

      2020 年 4 月 23 日,阿里云云开发平台联合天猫精灵智能应用平台共同发布,上线云开发服务功能,帮助天猫精灵智能应用的开发者以 0 启动成本、基于云原生Serverless架构服务,1分钟极致效率,完成一个标准技能应用的创建和部署。

      2020 年 4 月 28 日,阿里云云开发平台联合阿里前端委员会Serverless小组,共同发布基于 Ali Midway FaaS 框架的前后端一体通用 NodeJS 解决方案,帮助前端开发者以 0 启动成本、基于云原生Serverless架构服务,1分钟极致效率,完成一个标准WEB应用的创建和部署。

      在 Roadmap 中,还有微服务、小程序、大数据等场景解决方案将陆续上线。

      感受云开发平台的极致特性

      1 个开发界面
      打开浏览器就能开发不管你用什么设备,电脑、手机、平板不管是什么操作系统,Windows、MacOS、Linux、Android、iOS。

      1 套统一的业务环境
      统一的云上开发和业务环境(支持NodeJS,Java,PhP,Python, C# 等主流语言)登录即完成配置,无需等待,专注业务创新远程协同开发、所测即所得、测完即上线。

      1 个领先的架构
      基于业界领先的 Serverless 架构最快 1 秒钟部署按量付费不浪费,自动扩容不宕机。

      N 个行业应用场景
      解决方案模版化最快1分钟初始化一个行业应用(通用WEB应用,AIoT应用,微服务应用等等)99%时间聚焦在业务,开发更专注。

      了解云开发平台的运作方式
      云开发平台是一个可以满足开发者、研发团队完全基于「云+浏览器」就能完成日常开发工作的环境。它的设计理念是使自己成为团队大协同中的一环,它会跟阿里云诸多研发能力和工具进行集成,比如:云效企业协同底座、CloudIDE、Codeup、Flow 等等,籍由强大的阿里研发生态,为用户提供更大的协同研发可能,用户可以在使用云开发平台的时候,根据业务的需要,主动选择去开通使用更多类似于项目管理、需求管理、文档管理等其他服务。

      同时,为了帮助用户提供一个无缝应用阿里云服务的环境,云开发平台会跟阿里云的诸多云产品进行集成,随时为用户的使用而准备;用户可以在云开发平台创建基于各种场景解决方案的应用,并为每个应用选用不同的云服务,这些云服务会开通在用户的阿里云主账号之下,用户主动开通的各种云资源会按照用户的使用,正常地计量计费。

      云开发平台鼓励所有的场景解决方案尽可能多的基于阿里云的 Serverless 类型产品去提供服务。Serverless 类型的产品都具有实时弹性以及按量付费的特征,这可以帮助到商业化研发团队,以尽可能低的成本去实现自己的商业价值。

      云开发平台具体如何真实地帮到目标用户群体

      1、云开发平台如何帮助用户实现线上轻量化团队协同?

      我们联合云效,共同构建了一个在线研发团队的能力,团队规模从 1-10 人到 1000 人以上,全都免费提供,助力企业快速成长!

      1 分钟完成研发团队的在线化:在云开发平台,团队管理者创建好自己的企业,然后创建一批子账号分配给每一个团队成员,团队即完成了在线化;

      如果用户企业内已经有一套域账号系统,那么通过对接阿里云 SP 的 SAML 配置之后,能够方便实现用域账号的 SSO;

      如果团队并非组织关系型怎么办呢?在云开发平台,团队管理者创建好自己的企业,然后复制邀请链接,发送给那些并非组织关系里的成员,收到邀请的成员确认加入团队即完成团队的在线化;

      2、云开发平台如何帮助用户实现业务的快速启动呢?
      团队在线之后就要开始启动业务。

      新业务秒级启动:在云开发平台,团队管理员可以从应用场景中,选择一个成熟的行业应用解决方案,秒级完成应用的创建;

      业务开发人员直接进入业务开发,100% focus 在业务的开发交付:在云工作台,我们优化了以往传统线下研发模式中人人需要配置开发环境的弊端,将人人要做的事情,交由应用管理员一人执行,业务开发者登录云开发平台即开始业务开发;

      3、云开发平台如何帮助用户实现研发环境的升级呢?
      团队在线了,应用在线了,还剩下的环节就是代码和研发过程。

      安全可靠且免费的代码托管服务:云开发平台联合阿里云 Codeup,在云开发平台创建的每一个应用,都会自动分配一个免费的代码仓库,为用户提供安全可靠且免费的代码托管服务,帮助用户实现代码在线;

      功能强大的云端开发环境:云开发平台联合阿里经济体共建团队推出自研 CloudIDE,为用户提供功能强大,兼容 VS Code 插件生态的云端开发环境,内置NodeJS,Java,PhP,Python, C# 等主流语言开发环境,开箱即用,体验媲美本地;当然,开发者也可以选择将代码克隆到本地,继续以个人偏好的开发习惯进行开发,之后随时将代码同步到云端,做到云端与本地的实时同步;

      业界领先的Serverless架构:在每一个应用的背后,都有强大的阿里云Serverless产品矩阵构成的架构服务在支撑,API Gateway+Function Compute组合、VPC+ECI+EIP组合等等,保障每一个应用上线,都能稳稳地支撑,轻松助力用户不断攀登新的业绩高峰;

      0成本启动:基于强大的云开发平台服务以及阿里云Serverless架构矩阵,我们帮用户把 Serverless 如丝般顺滑落地到他们的业务之中,大胆开发,放心试错,无需为云开发平台支付任何费用;

      回顾

      在这次突如其来的疫情期间,所有人都隔离在家,也许千人规模的企业能够有内部的强大 IT 系统做支撑,整个企业仍然可以进行远程异地运转,但是对于广大的初创及成长中的企业,这种能力无疑是稀缺的,而在线,可能是未来的一个趋势和常态。随着阿里云云开发平台服务的上线,我们可以真正帮助到这些企业,通过用户业务视角、用户研发界面、提供云计算的开箱即用,去释放云上研发,Serverless架构的技术红利,推动云计算的普惠价值!


      阿里云新品发布会

      全面上云的时代,开发者应该如何转型才能跟上新基建发展的速度?今天中午 12 点阿里云新品发布为你揭晓答案,让你提前感受未来开发新标准,用 iPad 演示如何云开发,参与评论随机抽天猫精灵。

      识别下方二维码直达发布会:

      image.png


      image.png
      关注「Alibaba F2E」
      把握阿里巴巴前端新动向

      ]]>
      五分钟小知识:布隆过滤器原理和应用分析 | 算法必看系列四十二-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 image.png

      原文链接

      一、布隆过滤器出现的背景和要解决的问题

      Wikipedia 上面提到布隆过滤器早在 1970 年就被提出来,很难想象在当时那个年代它的主要用途是什么,估计当时提出也是一个数据模型吧。
      在互联网时代,每天会产生大量的数据,而且很多数据不是人产生的,而是机器产生的,就比如说是爬虫,每个网页被实际浏览的次数当中有一大半都是爬虫所致,那么这些数据怎么存储就是一个问题,有没有一个数据结构能够以很小的实际内存开销来存储这些数据呢?
      这也就是布隆过滤器要来解决的问题,要用尽量小的存储空间存储数据,还要使数据的获取更加快速、便捷。

      二、位图的概念

      在说布隆过滤器之前还是讲讲位图,BitMap,这个东西,先来回答这么一个问题,如果这个时候你需要判断一个整数是否在一堆整数当中,你会使用什么数据结构?散列表吗?这个当然是可行的,但是好不好呢?
      这里我的问题是只需要判断一个数在不在这一堆数里面,注意这里我要的结果其实只有两个,“在” 和 “不在”,如果说用散列把这些实际的数字全部存起来显然不是最理想的做法,我们只需要标记这个些数字存在即可,你可能会想到用 boolean 数组来做存储,还能不能继续优化?既然是 Binary 问题,那么一个 bit 是不是就够了,0 用来表示 false,1 用来表示 true,一个整数 4 个字节,32 bits,我们用一个 bit 就解决了,这样我们就把存储空间缩小了 32 倍。
      这个就是位图的概念,其就是用 bit 来作为存储数据的单位,数据量小的话,其优势可能并不明显,但是对于海量数据优势就很明显了。
      BitMap 其实就是一个整型数组,你也可以把其想象成 n * 32 的二维 bit 数组,但是这里还是有一个问题,上面我们讨论的仅仅是针对整数的存储是这样子,现实生活中,我们常常接触的会是字符串这类的数据,那这个该如何存储,我们该如何从字符串对应到实际的数组 index,这就要说到布隆过滤器了。

      三、布隆过滤器原理

      如果说要存储 1 亿个网站的 URL,你会使用什么样的数据结构?
      我们需要方便对应查找,因此 query 的时间复杂度不能过高,在正常的,我们经常接触的数据结构中,你可能会想到的是散列表、平衡二叉树、跳表等数据结构。
      我们来看看散列表,时间的话平均时间复杂度是 O(1),注意我这里说的是平均时间复杂度,哈希是会存在冲突的情况,这是你就要对比两个字符串上面的每个字符,完全符合条件才行,不符合还和继续找,继续对比;另外就是散列的存储空间,假如一个 URL 是 64 Bytes,那么 1 亿个 URL 大概是 6GB 的样子,但是对于散列来说的话这还没完,如果要尽量减少冲突的话,散列的实际 size 要比实际存储的数据的 size 要大,散列是这样,其他的数据结构,二叉树,跳表也都会有一些额外的开销,这些额外的开销都会导致实际存储当中不可避免的资源消耗。
      上面讲到的散列表其实就是数组,我们之前提到的位图也是数组,但是我们说到了字符串如何存储的问题,这时我们就需要借助哈希函数了,哈希函数会根据输入参数的特性返回一个数组 index,我们直接去这个 index 上查看即可。
      但是结合实际情况,我们有必要直接将整个 URL 存储起来吗?和位图的功能类似,布隆过滤器也仅仅是需要判断这个 URL 是不是在内存中,我们需要的答案是 “是” 或者 “不是”。
      但是这里有个问题,只要是哈希函数都会有冲突的问题,假如说我们之前标记了一个 URL 存在,但是这个时候冲突产生了,一个本身不存在的 URL 我们通过哈希函数发现其存在,这个时候就会产生误判,但是你要知道的是,这个误判也只是单向的,对于不同的 URL,哈希函数可能返回相同的值,但是如果说返回的这个值是不存在 的话,那么表示一定是不存在的,如果是存在的话,可能是存在,因为这时有可能是相同哈希值的 URL 存在,并不是当前的 URL 存在,这是就是我们说的误判的情况,简而言之就是 “false always false,true maybe true”。

      四、改进方法

      上面我们提到布隆过滤器会存在一定的误判率,这个时候我们需要做的就是尽量降低这个误判率。我们主要从两个方向进行优化,一个是布隆过滤器的 size,还有一个就是哈希函数。
      和散列表类似,这里也有一个装载因子的东西,它来保证实际的数据使用空间要低于总空间,这样的话才能使得冲突尽量的小;当然布隆过滤器是基于位图的,其占用的空间相比散列还是小的多的,一般实际空间和总空间 1:10 其实都不为过,这个比例绝大多数散列表是做不到的,特别是对于海量存储来说。因此这也就保证布隆过滤器的冲突发生几率要比散列表更加的小。
      另外一个影响冲突的因素是哈希函数,其实仅仅通过一个哈希函数来判断的话误判率确实会有点高,我们可以用多个哈希函数判断,这就好像有了多层保障,你必须保证满足条件1,条件2,条件3,…,才能被判定是 true,虽然说略微增加了时间的消耗,但是这些消耗往往都是常数级别的,误判率得到了有效的降低。
      所以,总的来说,增加布隆过滤器的大小,增加判断的哈希函数能够有效的降低误判率

      五、实际应用

      说了这么多,你可能会好奇布隆过滤器有啥用,只能返回一个 boolean 的值,有时还会出问题,在实际当中真的有用吗?
      在工程当中我们往往不会追求一个完美的结果,我们仅仅需要的是一个近似解,这就给布隆过滤器的应用提供了很大的空间
      在爬虫当中,机器需要知道这个网站是否被爬过,这里有上亿个网站,少爬一个其实没有多大区别,另外我们记录有多少用户访问了自己的 blog,这里也是近似,1000 个用户访问和 1002 个用户访问对我们影响并不大,我们只是看看这个大概的结果就行。
      对于海量数据来说,布隆过滤器的用途还是真的挺广泛的,它不需要特别大的存储空间就可以让计算机去做几乎正确的事情,这也是其它的传统的数据结构所不能达到的

      来源 | 五分钟学算法
      作者 | 程序员小吴

      ]]>
      SkyWalking agent 插件自动化测试实践-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 前言

      本文主要介绍SkyWalking agent 插件自动化测试框架组成及测试流程,以及一个实际的自动化测试testcase。

      SkyWalking 插件自动化测试框架介绍

      相关文档:

      [SkyWalking Plugin automatic test framework]
      (https://github.com/apache/skywalking/blob/master/docs/en/guides/Plugin-test.md)

      这个自动化测试框架主要包含下面几个部分:

      1. 测试环境docker镜像
      2. 自动化测试脚本
      3. testcase工程
      4. 结果验证工具

      测试环境docker镜像

      提供了两种测试环境:JVM-container 和 Tomcat-container,可以在创建testcase工程时选择使用的测试环境,官方推荐使用JVM-container。
      其中JVM-container 可以理解为用于运行基于SpringBoot的testcase项目,包含启动脚本,可以修改启动的JVM参数,灵活性更好。

      自动化测试脚本

      主要使用到两个脚本:

      • 创建testcase工程
        ${SKYWALKING_HOME}/test/plugin/generator.sh
        执行脚本后,根据提示输入testcase的类型,名称等信息,脚本自动创建一个可以编译运行的样例项目。
      • 运行测试案例
        ${SKYWALKING_HOME}/test/plugin/run.sh ${scenario_name}
        ${SKYWALKING_HOME}/test/plugin/run.sh -f ${scenario_name}
        ${SKYWALKING_HOME}/test/plugin/run.sh --debug ${scenario_name}

        参数说明:

        • -f 参数强制重新创建镜像,在修改SkyWalking agent或plugin后需要添加-f参数,否则不能更新测试镜像中的agent程序。只改动testcase时不需要-f参数,减少启动时间。
        • --debug 启用调试模式,推荐使用此参数,可以保留测试过程的logs

      testcase工程

      这里只介绍JVM-container类型的工程,实际上为基于SpringBoot的testcase应用。

      [plugin-scenario]
          |- [bin]
              |- startup.sh
          |- [config]
              |- expectedData.yaml
          |- [src]
              |- [main]
                  |- ...
              |- [resource]
                  |- log4j2.xml
          |- pom.xml
          |- configuration.yaml
          |- support-version.list
      
      [] = directory

      工程文件说明:

      文件/目录 说明
      bin/startup.sh testcase 应用启动脚本
      config/expectedData.yaml 测试结果验证数据
      configuration.yaml testcase 配置,包含类型、启动脚本、检测url等
      support-version.list testcase支持的版本列表,默认为空不会进行检查,可以改为all表示全部
      pom.xml maven 项目描述文件
      [src] testcase 源码目录

      其中对新手来说最难的是编写测试结果验证数据expectedData.yaml,数据格式不是很复杂,但要手写出来还是比较困难的。后面会提及一些技巧,可以从日志文件logs/validatolr.out中提取验证数据。

      测试结果验证工具

      SkyWalking 自动化测试工具的精髓所做应该就是自动验证测试结果数据,支持多种匹配条件表达式,可以灵活处理一些动态变化的数据。其中关键的是skywalking-validator-tools.jar工具,其源码repo为skywalking-agent-test-tool
      validator的代码量不大,通过阅读代码,可以了解expectedData.yaml的验证过程,理解验证数据的格式。

      自动化测试流程

      bash ./test/plugin/run.sh --debug xxxx-scenario
      -> 准备测试的workspace 
      -> 编译testcase工程
      -> 启动plugin-runner-helper 生成docker启动脚本等
      -> scenario.sh 
          -> 启动测试环境docker实例 
          -> docker容器中执行 /run.sh
              -> collector-startup.sh
                  -> 启动skywalking-mock-collector(测试数据收集服务)
              -> testcase/bin/startup.sh 
                  -> 启动testcase应用(-javaagent加载skywalking-agent.jar)
              -> 循环healthCheck,等待testcase应用启动完毕
              -> 访问entryService url,触发测试用例
              -> 接收测试数据,写入到data/actualData.yaml文件
              -> 启动skywalking-validator-tools.jar验证测试结果数据
              -> 结束

      设计测试用例

      测试结果验证工具只能收集testcase应用的APM数据,比如span和logEvent等,不能收集http请求的返回内容,但可以收集到请求的状态码。

      测试用例交互过程

      这里仅介绍通过http请求交互,收集http相关数据,其它的数据与具体插件相关。比如测试redis apm插件时,可以收集到redis事件,包含执行的redis命令语句。
      通过test/plugin/generator.sh命令生成测试用例中包含两个url,一个是healthCheck,一个是entryService。
      1)healthCheck一般不需要管,用于探测testcase应用是否启动成功。如果编写的testcase有需要初始化的数据,请在healthCheck返回成功之前进行处理。

      2)entryService是测试的入口url,healthCheck通过后,会接着访问entryService。可以在entryService的方法中进行调用测试方法,失败时返回4xx/5xx状态码。
      这里要注意一个问题:
      org.apache.skywalking.apm.testcase.*包下面的类不会被SkyWalking agent增强,这意味着这个包里面所有的类都不会被插件增强处理,比如标注了@Controller、@Component等的类并不会被apm-spring-annotation-plugin-*.jar 插件增强。如果要测试类增强的相关代码在testcase中,则要将代码放到这个包里面test.org.apache.skywalking.apm.testcase.*

      如何通过收集的APM数据判断测试成功或者失败?

      1)http处理成功返回200/3xx时收集到span信息没有status_code,处理异常返回4xx/5xx错误时会产生一个tag记录status_code,可以用于验证区分测试结果。参考代码如下:

      @RequestMapping("/dosomething")  
      public ResponseEntity dosomething() {  
        // check testcase is successful or not  
        if (isTestSuccess()) {  
            return ResponseEntity.ok("success");  
        } else {  
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("failure");  
        }  
      }

      2)还可以通过抛出异常来产生status_code和logEvent

        @RequestMapping("/xxx-scenario")  
        @ResponseBody  
        public String testcase() throws HttpStatusCodeException {  
          if (isTestSuccess()) {
              return "success";
          }
          throw new RuntimeException("failure");  
        }

      编写测试结果验证数据(expectedData.yaml)

      1. 启用调试模式 (--debug),保留日志文件目录logs
        bash ./test/plugin/run.sh --debug mytest-scenario
      2. 从日志文件提取收集的数据
        日志文件目录:skywalking/test/plugin/workspace/mytest-scenario/all/logs

      收集的数据可以从日志文件validatolr.out从提取到,找到后面的actual data:

      [2020-06-10 07:31:56:674] [INFO] - org.apache.skywalking.plugin.test.agent.tool.validator.assertor.DataAssert.assertEquals(DataAssert.java:29) - actual data:
      {
        "segmentItems": [
          {
            "serviceName": "mytest-scenario",
            "segmentSize": "2",
            "segments": [
              {
              ....
              }
            ]
          }
        ]
      }
      1. 将actual data的json数据转换为yaml
        打开其他测试场景的expectedData.yaml,如httpclient-3.x-scenario/config/expectedData.yaml 的第一段内容:
      segmentItems:  
      - serviceName: httpclient-3.x-scenario  
        segmentSize: ge 3  
        segments:  
        - segmentId: not null  
          spans:  
          - operationName: /httpclient-3.x-scenario/case/context-propagate  
            operationId: 0  
            parentSpanId: -1  
            spanId: 0  
            spanLayer: Http  
            startTime: nq 0  
            endTime: nq 0  
            componentId: 1  
            isError: false  
            spanType: Entry  
            peer: ''  
            tags:  
            - {key: url, value: 'http://localhost:8080/httpclient-3.x-scenario/case/context-propagate'}  
            - {key: http.method, value: GET}  
            refs:  
            - {parentEndpoint: /httpclient-3.x-scenario/case/httpclient, networkAddress: 'localhost:8080',  
              refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not  
                null, parentService: httpclient-3.x-scenario, traceId: not null}  
            skipAnalysis: 'false'

      expectedData.yaml是用于检查测试结果的匹配模板,只有简单的几种匹配表达式如下表:

      Operator for number

      | Operator | Description |
      | :--- | :--- |
      | nq | Not equal |
      | eq | Equal(default) |
      | ge | Greater than or equal |
      | gt | Greater than |

      Operator for String

      | Operator | Description |
      | :--- | :--- |
      | not null | Not null |
      | null | Null or empty String |
      | eq | Equal(default) |

      比如segmentId是随机生成的,那么可以写成segmentId: not null 这样就可以匹配任意字符串。开始时间是变化的可以写成startTime: nq 0,只判断其是否大于0就可以。
      对照获取到的actual data json,修改对应的字段就可以了。可以忽略检查healthCheck的数据,只需要写上关键的segment。看一个案例,actual data 如下:

      {
        "segmentItems": [
          {
            "serviceName": "mytest-scenario",
            "segmentSize": "2",
            "segments": [
              { 
                  ... healthCheck ...
              },
              { 
                "segmentId": "ab32f6a2774347958318b0fb06ccd2f0.33.15917743102950000",
                "spans": [
                  { 
                    "operationName": "/case/mytest-scenario",
                    "operationId": "0",
                    "parentSpanId": "-1",
                    "spanId": "0",
                    "spanLayer": "Http",
                    "tags": [
                      { 
                        "key": "url",
                        "value": "http://localhost:8080/case/mytest-scenario"
                      },
                      { 
                        "key": "http.method",
                        "value": "GET"
                      }
                    ],
                    "startTime": "1591774310295",
                    "endTime": "1591774310316",
                    "componentId": "14",
                    "spanType": "Entry",
                    "peer": "",
                    "skipAnalysis": "false"
                  }
                ] 
              }
            ]       
          }         
        ]           
      }             

      对应的expectedData.yaml(忽略检查healthCheck的数据):

      segmentItems:  
        - serviceName: mytest-scenario  
          segmentSize: ge 1  
          segments:  
            - segmentId: not null  
              spans:  
                - operationName: /case/mytest-scenario  
                  operationId: 0  
                  parentSpanId: -1  
                  spanId: 0  
                  spanLayer: Http  
                  startTime: nq 0  
                  endTime: nq 0  
                  componentId: ge 1  
                  isError: false  
                  spanType: Entry  
                  peer: ''  
                  tags:  
                    - {key: url, value: 'http://localhost:8080/case/mytest-scenario'}  
                    - {key: http.method, value: GET}  
                  skipAnalysis: 'false'

      常见错误处理

      • docker 容器实例名冲突
        ./test/plugin/run.sh 出现下面的错误:

      docker: Error response from daemon: Conflict. The container name "/xxxx-scenario-all-local" is already in use by container "42cdee17e557bb71...". You have to remove (or rename) that container to be able to reuse that name.
      解决办法:
      删除上次测试失败留下来的容器实例:docker rm xxxx-scenario-all-local

      编写自动化测试testcase

      1. 生成testcase工程

      > cd skywalking
      > bash ./test/plugin/generator.sh
      Sets the scenario name
      >: mytest-scenario
      Chooses a type of container, 'jvm' or 'tomcat', which is 'jvm-container' or 'tomcat-container'
      >: jvm
      Gives an artifactId for your project (default: mytest-scenario)
      >: 
      Sets the entry name of scenario (default: mytest-scenario)
      >: 
      scenario_home: mytest-scenario
      type: jvm
      artifactId: mytest-scenario
      scenario_case: mytest-scenario
      
      Please confirm: [Y/N]
      >: y
      [INFO] Scanning for projects...

      2. 修改配置文件

      修改mytest-scenario/support-version.list,添加支持的版本,这里用全部版本all。注意,默认没有指定版本,不会启动测试场景。

      # lists your version here  
      all

      3. 编写测试用例

      @RestController  
      @RequestMapping("/case")  
      public class CaseController {  
        
        private static final String SUCCESS = "Success";  
        
        @RequestMapping("/mytest-scenario")  
        @ResponseBody  
        public ResponseEntity testcase() {  
              //这里简单模拟,随机返回成功或者失败
              SecureRandom random = new SecureRandom();  
              if (random.nextBoolean()) {  
                  return ResponseEntity.ok(SUCCESS);  
              } else {  
                  return ResponseEntity.notFound().build();  
              }  
          }  
        
        @RequestMapping("/healthCheck")  
        @ResponseBody  
        public String healthCheck() {  
            // your codes  
            return SUCCESS;  
          }  
      }

      4. 本地测试testcase

      bash ./test/plugin/run.sh --debug mytest-scenario

      5. 提取测试收集的数据

      从日志文件提取收集的数据,actual data部分。
      日志文件:skywalking/test/plugin/workspace/mytest-scenario/all/logs/validatolr.out

      [2020-06-10 09:00:03:655] [INFO] - org.apache.skywalking.plugin.test.agent.tool.validator.assertor.DataAssert.assertEquals(DataAssert.java:29) - actual data:
      {
        "segmentItems": [
          {
            "serviceName": "mytest-scenario",
            "segmentSize": "2",
            "segments": [
              {
                "segmentId": "bfddda9bb70f49c694a90924b258a6da.32.15917795967760000",
                "spans": [
                  {
                    "operationName": "/mytest-scenario/case/healthCheck",
                    "operationId": "0",
                    "parentSpanId": "-1",
                    "spanId": "0",
                    "spanLayer": "Http",
                    "tags": [
                      {
                        "key": "url",
                        "value": "http://localhost:8080/mytest-scenario/case/healthCheck"
                      },
                      {
                        "key": "http.method",
                        "value": "HEAD"
                      }
                    ],
                    "startTime": "1591779596801",
                    "endTime": "1591779597069",
                    "componentId": "1",
                    "spanType": "Entry",
                    "peer": "",
                    "skipAnalysis": "false"
                  }
                ]
              },
              {
                "segmentId": "bfddda9bb70f49c694a90924b258a6da.33.15917795971310000",
                "spans": [
                  {
                    "operationName": "/mytest-scenario/case/mytest-scenario",
                    "operationId": "0",
                    "parentSpanId": "-1",
                    "spanId": "0",
                    "spanLayer": "Http",
                    "tags": [
                      {
                        "key": "url",
                        "value": "http://localhost:8080/mytest-scenario/case/mytest-scenario"
                      },
                      {
                        "key": "http.method",
                        "value": "GET"
                      }
                    ],
                    "startTime": "1591779597132",
                    "endTime": "1591779597141",
                    "componentId": "1",
                    "spanType": "Entry",
                    "peer": "",
                    "skipAnalysis": "false"
                  }
                ]
              }
            ]
          }
        ]
      }
      

      6. 编写expectedData.yaml

      segmentItems:  
        - serviceName: mytest-scenario  
          segmentSize: ge 1  
          segments:  
            - segmentId: not null  
              spans:  
                - operationName: /case/mytest-scenario  
                  operationId: 0  
                  parentSpanId: -1  
                  spanId: 0  
                  spanLayer: Http  
                  startTime: nq 0  
                  endTime: nq 0  
                  componentId: ge 1  
                  isError: false  
                  spanType: Entry  
                  peer: ''  
                  tags:  
                    - {key: url, value: 'http://localhost:8080/case/mytest-scenario'}  
                    - {key: http.method, value: GET}  
                  skipAnalysis: 'false'
      ]]>
      揭秘 SIGCOMM 20‘ 论文:阿里云网络洛神 VTrace 系统 | 技术日报(7期)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 每日干货推荐

      揭秘 SIGCOMM 20‘ 论文:阿里云网络洛神 VTrace 系统>>>

      VTrace 是一个解决云网络持续性丢包问题的自动化诊断系统,核心思想是“任务-匹配-染色-采集-分析”,结合大数据技术,旨在实时快速的自动分析出云网络端到端的流量拓扑路径,并给出准确的问题原因和解决方案,让网络运维不再需要那么“专业”,那么“复杂”。

      免费下载!《阿里工程师的自我修养》公开10位阿里大牛解决问题的思维方式>>>

      阿里技术公布一波阿里P8、P9技术大牛的思维模型,将他们的思维模式呈现出来。你可以在阿里资深专家职业生涯的真切感悟中,找到应对危机的最佳方法。《阿里工程师的自我修养》现已正式公开,可免费下载阅读。

      更多精彩文章

      预览速度提升30倍,这是什么黑科技?(天猫618之3D渲染篇)>>>

      天猫618宣布的 3D 购物时代,相信有很多小伙伴好奇,这背后有哪些“黑科技”?橙子从以下三点为你揭秘——3D实景复刻、3D渲染、3D算法,上周讲了《天猫618宣布开启3D购物时代,实景逛街背后的技术是这样实现的》,本期将和大家分享 AceRay 极速渲染。

      一张图讲清楚淘宝直播背后技术( 赠送多媒体前端手册)>>>

      2020年,直播带货火爆全网。想一探淘宝直播背后的前端技术?本文将带你进入淘宝直播前端技术的世界。 对于大多数前端工程师来说,音视频技术是一个比较少涉足的领域,本文涵盖了流媒体技术中的文本、图形、图像、音频和视频多种理论知识,涉及到播放器、web媒体技术、主流框架等介绍,只需要花上一点点时间,你将进入前端多媒体的领域。

      如何画好一张架构图?>>>

      架构图是什么?为什么要画架构图?如何画?有哪些方法?本文从架构的定义说起,分享阿里文娱高级技术专家箫逸关于画架构图多年的经验总结,并对抽象这一概念进行了深入地讨论。较长,同学们可收藏后再看。

      精品公开课

      云原生下的开发测试>>>

      本次分享将结合实际场景,演绎阿里在管理测试环境中积累的经验和方法,并带来一款面向采用云原生技术栈的IT企业和开发者的测试环境治理开源解决方案。

      新一代高效Git协同模型>>>

      本次分享将会介绍在阿里巴巴内部是孵化这个Git新特性的过程,将其贡献到社区所经历的功能演进,以及该特性将会给代码开发模式带来的革新。

      快速交付云原生应用的 3 种发布策略详解>>>

      交付策略决定交付结果,通常一次交付主要包含网络的变更和服务载体的变更,如何保证决定了交付过程中的可靠性、连续性、稳定性?本次课程将介绍 3 种常用的应用交付策略。

      ]]>
      从零入门 Serverless | 一文详解 Serverless 技术选型-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 6.12头图.png

      作者 | 李国强  阿里云资深产品专家

      本文整理自《Serverless 技术公开课》。关注“Serverless”公众号,回复“入门”,即可获取 Serverless 系列文章 PPT。

      今天来讲,在 Serverless 这个大领域中,不只有函数计算这一种产品形态和应用类型,而是面向不同的用户群体和使用习惯,都有其各自适用的 Serverless 产品。例如面向函数的函数计算、面向应用的 Serverless 应用引擎、面向容器的 Serverless Kubernetes,用户可以根据自己的使用习惯、使用场景或者应用类型,去选择使用什么样的 Serverless 产品。下面通过本文给大家介绍一下,阿里云都有哪些可供大家选择的 Serverless 产品。

      Serverless 产品及分层

      众所周知,最早提出 Serverless 的是 AWS,其在 Serverless 领域的旗舰产品是 function compute。同样阿里云也有函数计算的产品,帮助用户构建 Serverless 函数。但 Serverless 不仅仅是函数,如下图所示,其实用户会期望在应用、容器等层面也能够享受到 Serverless 的好处,包括按量付费、极致弹性等,这样也更符合用户原有的使用习惯。

      幻灯片2.JPG

      在上图中,大家能够看到,阿里云针对函数、应用和容器都推出了对应的 Serverless 产品,用户可以针对自己的使用场景选择不同的产品。

      函数计算 

      1. 函数计算介绍

      幻灯片3.JPG

      上图展示了函数计算的使用方式。从用户角度,他需要做的只是编码,然后把代码上传到函数计算中。这个时候还不会产生费用,只有到被调用的时候才有费用。调用的方式可以是产品提供的 API/SDK,也可以通过一些事件源,比如阿里云的 OSS 的事件。比如用户往 OSS 里的某一个 bucket 上传了一个文件,希望这个文件被自动处理;比如上传一个 zip 包,希望能够自动解压到另外一个 bucket,这都是很典型的函数场景。

      另外,函数计算能够提供非常好的弹性能力,最终的费用是根据时长和内存数进行计费的,如果调用量小的话,只会有很少的费用。并且它在语言方面也非常丰富,常用的 nodejs、php、python、java 都直接支持。同时提供自定义的运行环境,可以支持任意的可执行的语言。

      2. 函数计算典型场景

      幻灯片4.JPG

      从使用场景来说,主要有三类:

      • Web 应用:可以是各种语言写的,这种可以使用 Serverless 框架新编写的程序,也可以是已有的应用。比如小程序后端、或者发布到 API 市场的 API 后端应用等;
      • 对计算能力有很强的弹性诉求的应用:比如 AI 推理、音视频处理、文档转换等;
      • 事件驱动型的应用:比如通过其他阿里云产品驱动的场景、Web Hook、定时任务等,函数计算已经与很多产品进行了打通,比如对象存储、表格存储、定时器、CDN、日志服务、云监控等,可以非常快速地组装出一些业务逻辑。

      3. 函数计算核心竞争力

      幻灯片5.JPG

      函数计算对客户的一个最大的价值,就是能够让用户只关注自己的业务逻辑开发,完全不需要管理运维,诸如计算资源、网络设置等都不需要关心。在隔离性上提供 vm 级别的隔离,保证用户在运行时的数据安全、运行时安全等;在可用性方面默认提供 3az 的高可用架构,保证客户默认就是高可用的最佳实践架构;在弹性方面,可以做到毫秒级的弹性效率,满足客户突发的流量冲击;在计费方面也非常灵活,真正按照用户的请求情况进行收费,也支持对 long run 的应用更友好的预付费模式。

      Serverless 应用引擎

      1. SAE 概述

      幻灯片6.JPG

      SAE 是业内首款面向应用的 Serverless Paas 平台。这个产品以面向应用的视角,帮助用户在不做任何修改的前提下把存量应用上到云端。在资源层,用户不再需要自己管理和运维机器及集群,只需要关注自己应用所需要使用的规格以及实例数,不再需要关心底层是虚机还是容器。

      SAE 从资源层面提供计算资源、弹性、隔离性等能力,让用户只需要关注自己的应用。在应用层,SAE 提供了监控、日志、微服务治理等能力,帮助用户解决应用可观测性和治理需求。同时提供网络配置、流量控制能力,提供了和 CICD 良好的集成,用户可以使用已有 CICD 部署到 SAE,比如 jenkins、云效等,可以说覆盖了应用上云的完整场景。

      2. SAE 典型场景

      幻灯片7.JPG

      SAE 有几个典型的使用场景,一个是存量业务上云,特别是微服务、java 应用,同时也支持其他语言的单体应用,都能够通过 SAE 这个平台运行在阿里云上,并且不需要做任何代码的修改。在行业方面,SAE 特别适合有比较大的流量波动的在线业务,比如电商大促、在线教育等行业的场景。另外 SAE 作为应用平台也可以被上层的行业 Saas 所集成,帮助用户更快地构建行业 Saas。

      3. SAE 特性

      幻灯片8.JPG

      通过上面的场景介绍,我们可以看到 SAE 除了 Serverless 体验本身所带来的极致弹性、免运维等特性之外,重点在应用层给用户提供全栈的能力,包括对微服务的增强支持,以及整合了和应用息息相关的能力,包括配置、监控、日志、流量控制等。再加上用户零代码的改动,是企业在线业务平滑上云非常好的选择。

      Serverless Kubernetes

      1. ASK 概述

      幻灯片9.JPG

      另一个阿里云提供的 Serverless 产品是 Serverless K8s。但是 K8s 怎么还能 Serverless 呢?这就需要先了解一下技术架构的演进历程。

      最早的时候大家都把 Docker 镜像部署在虚机里,用户需要购买 ECS,然后部署镜像,最后是网络的一些配置,比如 SLB、EIP 等。在这个过程中,用户需要自己完成部署动作,扩容需要自己重复上面的动作,或者自己构建一套自动化脚本,相对来说成本和稳定性都比较低。

      之后有了 K8s 来帮大家解决容器编排的问题。这种标准化的方式确实大大提高了大家的生产力。用户通过使用 deployment、service 等标准的 K8s 的方式进行编排,并进行部署。但 K8s 的运维和管理还是相对比较复杂的,技能要求比较高,用户需要运维 ECS 以及通过 ECS 构建出来的 K8s。另外一个痛点时 K8s 集群里的 ECS 是需要预先购买的,如果客户的负载有比较大的波动,就会出现比较多的资源浪费。虽然技术上也有解决方案,比如 worker node 的弹性,但这对于初级用户来说,还是有比较高的复杂度。

      那有没有一种方案可以让用户既能享受到 K8s 提供的容器编排能力,又能够不需要关心 ECS 和 K8s 的运维、管理和弹性问题呢?这就是 Serverless K8s 的方案。对应到阿里云的产品就是 ASK。在 ASK 的方案里,用户创建一个 ASK 集群,但不需要指定任何 ECS 节点,然后通过标准的 K8s 容器编排、deployment 等部署镜像。ASK 会根据用户的负载需求,自动在底层资源池构建需要的 POD 并进行弹性伸缩,用户不再需要关心容量规划、ECS 机器运维、资源限制等 LaaS 层的问题,非常便利。

      2. ASK 典型场景

      幻灯片10.JPG

      那 ASK 主要用在哪些场景里呢?首先可以用来跑在线业务,部署模式灵活,可以是 deployment、helm chart 等所有的 K8s 原生模式,特别是能够很好地应对突发流量,极致弹性,可以在 30 秒完成 500 个容器实例的弹性。这样的弹性效率,可以很好地支撑大数据计算类的任务,比如 Spark、Presto 等,也可以在需要的时候即时获取资源,支撑 10000 以上 Pod 的规格,有效降低客户成本。

      另外一个非常适合的场景是用来构建随需启动的构建任务,比如在 ASK 中运行 jenkins、Gitlab-Runner 等。在有构建任务的时候,即时启动。没有任务的时候 0 消费,成本做到最低。这里只是列出了一些例子的场景,实际上基于 ASK 的这个特性,用户可以运行很多 K8s 原生的需要极致弹性的工作负载。

      3. ASK 特性

      幻灯片11.JPG

      ASK 完全容器部署,通过容器进行隔离。在使用的过程中,用户无需运维 ECS 或者 K8s 集群,也不需要考虑集群升级、容量规划、OS 及系统软件问题等事情,理论上可以提供无限的弹性容量。因为是完全按照使用量进行收费,所以就不需要为限制资源付费。

      总结

      总结一下,可以看到阿里云今天在 Serverless 领域有非常多样的产品,既有面向函数的函数计算,用户可以只关注代码,快速开发交付;也有面向应用的 Serverless 应用引擎,让用户更关注应用视角,并且提供了围绕应用的一系列能力,包括监控、日志、流量等能力的集成;对于更习惯 K8s 生态的用户,ASK 让用户在不改变当前 K8s 使用习惯的前提下,也能享受到 Serverless 的优势。多样的产品,在满足不同用户诉求的同时,也使用户体验到了 Serverless 的免运维、极致弹性、按量付费等优势。

      课程推荐

      为了更多开发者能够享受到 Serverless 带来的红利,这一次,我们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。

      点击即可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      通过合理调度设备,降低企业设备成本-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 背景

      岩鼠平台,支持云真机租用,也支持自动化测试。其背后都是大量设备资源的使用,而真机测试设备及自动化测试的设备资源是否能合理利用,关系到企业的设备成本。如何达到设备利用最大化呢,有几个维度需要考虑:

      • 在保证真机测试需求下,合理利用空闲机器进行自动化测试
      • 不同自动化测试类型测试时长不同
      • 不同测试类型对设备机型要求不同
      • 更大程度的延缓设备老化

      为了保证真机测试需求,同时提升整体自动化测试的成功率和稳定性,也为了能让各台设备达到最大利用率,岩鼠平台做了不少设计和优化,积累了一些经验,在此与各位读者分享一下。

      解决方案

      整体概述

      首先,由于云真机和自动化都会占用机器,我们可以根据云真机的使用需求,为云真机和不同的自动化测试各自分配自己的设备池子。但由于需求是在一定范围内波动,不是完全定量的,因此可以将部分设备设置为共用,这样能最大程度的将设备利用起来。

      而其中的难点又在于,对于自动化测试来说,有多个设备池子,不同测试对设备的要求又不同,通过什么样的策略才能更大程度的保障任务成功率和设备利用率呢?接下来将对此重点介绍。

      自动化测试中,从任务创建开始,到任务在各个具体设备上执行,总共经历了三个阶段,分别是:设备准备、设备规则筛选、设备排位与占用,可选设备逐阶段减少,大体如下:

      阶段一:设备准备

      本阶段主要是初步筛选出适合待分配任务的基础设备池,其中主要有以下流程:

      1. 若用户创建任务时有指定的分配策略,则预加载,否则使用默认策略,例如目前岩鼠平台开放了“随机”策略,则在此处会预加载“随机”策略设置的关联约束和筛选偏好;
      2. 不同的测试类型(如兼容性,或者是深度智能遍历),需要加载不同的设备组,一是考虑到术业有专攻,里面都是经过平台精心挑选和分类的适合该测试场景的设备,二是尽量减少彼此之间的影响,例如对于同一台设备,平台是会争取避免上一个跑偏重于性能测试的任务,而下一个却是跑偏重于稳定性的任务;
      3. 筛选用户权限范围内的设备;
      4. 准备好设备的各种筛选属性,并根据一定时间内的已运行任务数和运行时间,通过特定公式算出一个值,可以认为这个值就是该设备的疲劳系数。

      通过上述可知,此处提到的策略和设备组,都是平台已经预置好的,也会随着已有的运行结果和实际的业务发展而持续演变,并结合大数据平台进行优化,这里就不再展开了。

      阶段二:设备规则筛选

      从处理逻辑来看,这阶段更像是一个规则引擎,根据需求可加载不同的插件,进一步优选出符合实际测试要求的可运行设备池,目前已支持的有:

      • 根据app设置去筛选所需设备的最低/最高版本,例如某些应用只能运行在Android 6.0以上的设备,这也是保证任务成功的前提条件之一;
      • 默认会根据疲劳系数和过往任务成功率进行优先级排列,也可以接受自定义权值,例如是看重成功率,则将成功率的权值提高,或者是想要优先使用较“新”的设备,则可以结合疲劳系数和入库时间传入权值,这些都会影响到设备的推荐分数;
      • 默认是“随机”策略,但也可定制其它策略,例如Android的API遍历,就是从每个Android版本里面各挑出一台设备组成测试,以达到测试版本兼容性的目的;
      • 可指定特定品牌或特定机型进行测试,当然也少不了TOP10、TOP20、TOP50的维度;
      • ……

      这里的筛选插件相互之间基本是“且”的关系,即对设备进行层层过滤,若有特殊需求,也可以配置出“或”的关联;对于有经验的开发者,查看接口文档之后,一般一两个小时即可开发出一个新插件,测试通过之后就能注册到容器中,从而对外提供服务。

      阶段三:设备排位与占用

      经过层层筛选之后,最终落实到占用设备的环节,又细分出两大步骤:

      1. 经过上面两个步骤,每台设备都会有一个推荐分数,此处会根据分数进行区间划分(可以考虑下为啥不直接按分数高低倒序分配),高分区间会优先被占,分到同一区间的则会进行疲劳系数排队,系数越小,排名越前,最终得到提权后的设备队列;若排名也一致,则再进行随机出列的处理;
      2. 从队列取出第一台设备,若设备能被占用,则落实到该子任务;若设备占用失败,则继续下一设备的尝试,直到没有空余设备,或清空了子任务列表,则整体分配完毕。

      优缺点

      人无完人,更何况是管理了这么多“性格各异”的设备的方案呢,先说说好的方面:

      • 由于疲劳系数的引入,除开某些崩得太严重的个体,用于自动化测试的设备都有机会、且平均地被分配到任务,需要注意的是,这里说的平均,不是绝对的平均,还是要看设备本身的素质,若能力越强,分到的任务也多,反之亦然;
      • 筛选插件可不断扩展,快速满足各种分配需求;
      • 毕竟最终都是对口的设备,可有效提高任务成功率。
        至于不好的,在设计之初也是有所预见:
      • 存在一些重复或无用的查询问题,例如算了好几台设备的疲劳系数,但可能在筛选中早早就被过滤了;
      • 并发性能受到约束,考虑这么一个场景:一个任务不要的设备,却是另一个任务需要的。

      未来方向

      更进一步提升设备分配策略和算法,更大程度的提升设备利用率,在满足业务需求的情况下,降低成本:

      • 彻底的分布式处理;
      • 结合大数据,构筑更全面的优选规则;
      • 真正AI的引入,实现更精准、更快速的推荐,减少人工开发。

      免费试用岩鼠云设备平台

      赶快来试用下岩鼠云真机吧!每天都有免费试用体验额度哦。

      岩鼠云设备平台-云端设备,触手可及

      ]]>
      涂鸦智能 dubbo-go 亿级流量的实践与探索-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 1.png

      作者 | 潘天颖,Github ID @pantianying,开源爱好者,就职于涂鸦智能

      dubbo 是一个基于 Java 开发的高性能的轻量级 RPC 框架,dubbo 提供了丰富的服务治理功能和优秀的扩展能力。而 dubbo-go 在 java 与 golang 之间提供统一的服务化能力与标准,是涂鸦智能目前最需要解决的主要问题。本文分为实践和快速接入两部分,分享在涂鸦智能的 dubbo-go 实战经验,意在帮助用户快速接入 dubbo-go RPC 框架,希望能让大家少走些弯路。另外,文中的测试代码基于 dubbo-go版本 v1.4.0。

      dubbo-go 网关实践

      2.png

      dubbo-go 在涂鸦智能的使用情况如上图,接下来会为大家详细介绍落地细节,希望这些在生产环境中总结的经验能够帮助到大家。

      1. 背景

      在涂鸦智能,dubbo-go 已经作为了 golang 服务与原有 dubbo 集群打通的首选 RPC 框架。其中比较有代表性的 open-gateway 网关系统(下文统一称 gateway,开源版本见 https://github.com/dubbogo/dubbo-go-proxy)。该 gateway 动态加载内部 dubbo 接口信息,以HTTP API 的形式对外暴露。该网关意在解决上一代网关的以下痛点。

      • 通过页面配置 dubbo 接口开放规则,步骤繁琐,权限难以把控;
      • 接口非 RESTful 风格,对外部开发者不友好;
      • 依赖繁重,升级风险大;
      • 并发性能问题。

      2. 架构设计

      针对如上痛点,随即着手准备设计新的 gateway 架构。首先就是语言选型,golang 的协程调用模型使得 golang 非常适合构建 IO 密集型的应用,且应用部署上也较 java 简单。

      经过调研后我们敲定使用 golang 作为 proxy 的编码语言,并使用 dubbo-go 用于连接 dubbo provider 集群。provider 端的业务应用通过使用 java 的插件,以注解形式配置 API 配置信息,该插件会将配置信息和 dubbo 接口元数据更新到元数据注册中心(下图中的 redis )。这样一来,配置从管理后台页面转移到了程序代码中。开发人员在编码时,非常方便地看到 dubbo 接口对外的 API 描述,无需从另外一个管理后台配置 API 的使用方式。

      3.png

      3. 实践

      从上图可以看到,网关能动态加载 dubbo 接口信息,调用 dubbo 接口是基于 dubbo 泛化调用。泛化调用使 client 不需要构建 provider 的 interface 代码,在 dubbo-go 中表现为无需调用 config.SetConsumerService 和 hessian.RegisterPOJO 方法,而是将请求模型纯参数完成,这使得 client 动态新增、修改接口成为可能。在 apache / dubbo-sample / golang / generic / go-client 中的有泛化调用的演示代码。

      func test() {
          var appName = "UserProviderGer"
          var referenceConfig = config.ReferenceConfig{
              InterfaceName: "com.ikurento.user.UserProvider",
              Cluster:       "failover",
              Registry:      "hangzhouzk",
              Protocol:      dubbo.DUBBO,
              Generic:       true,
          }
          referenceConfig.GenericLoad(appName) // appName is the unique identification of RPCService
          time.Sleep(3 * time.Second)
          resp, err := referenceConfig.GetRPCService().(*config.GenericService).
              Invoke([]interface{}{"GetUser", []string{"java.lang.String"}, []interface{}{"A003"}})
          if err != nil {
              panic(err)
          }
      }

      泛化调用的实现其实相当简单。其功能作用在 dubbo 的 Filter 层中。Generic Filter 已经作为默认开启的 Filter 加入到 dubbo Filter 链中。其核心逻辑如下:

      func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
          if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
              oldArguments := invocation.Arguments()
              if oldParams, ok := oldArguments[2].([]interface{}); ok {
                  newParams := make([]hessian.Object, 0, len(oldParams))
                  for i := range oldParams {
                      newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
                  }
                  newArguments := []interface{}{
                      oldArguments[0],
                      oldArguments[1],
                      newParams,
                  }
                  newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
                  newInvocation.SetReply(invocation.Reply())
                  return invoker.Invoke(ctx, newInvocation)
              }
          }
          return invoker.Invoke(ctx, invocation)
      }

      Generic Filter 将用户请求的结构体参数转化为统一格式的 map(代码中的 struct2MapAll ),将类( golang 中为 struct )的正反序列化操作变成 map 的正反序列化操作。这使得无需 POJO 描述通过硬编码注入 hessain 库。

      从上面代码可以看到,泛化调用实际需要动态构建的内容有 4 个,ReferenceConfig 中需要的 InterfaceName、参数中的 method、ParameterTypes、实际入参 requestParams。

      那么这些参数是如何从 HTTP API 匹配获取到的呢?

      这里就会用到上文提到的 provider 用于收集元数据的插件。引入插件后,应用在启动时会扫描需要暴露的 dubbo 接口,将 dubbo 元数据和 HTTP API 关联。插件使用方法大致如下,这里调了几个简单的配置作为示例,实际生产时注解内容会更多。

      4.png

      最终获得的 dubbo 元数据如下:

      {
          "key": "POST:/hello/{uid}/add",
          "interfaceName": "com.tuya.hello.service.template.IUserServer",
          "methodName": "addUser",
          "parameterTypes": ["com.tuya.gateway.Context", "java.lang.String", "com.tuya.hello.User"],
          "parameterNames": ["context", "uid", "userInfo"],
          "updateTimestamp": "1234567890",
          "permissionDO":{},
          "voMap": {
              "userInfo": {
                  "name": "java.lang.String",
                  "sex": "java.lang.String",
                  "age": "java.lang.Integer"
              }
          },
          "parameterNameHumpToLine": true,
          "resultFiledHumpToLine": false,
          "protocolName": "dubbo",
        .......
      }

      Gateway 从元数据配置中心订阅到以上信息,就能把一个 API 请求匹配到一个 dubbo 接口。再从 API 请求中抓取参数作为入参。这样功能就完成了流量闭环。

      以上内容,大家应该对此 gateway 的项目拓扑结构有了清晰的认知。我接着分享项目在使用 dubbo-go 过程中遇到的问题和调优经验。19 年初,当时的 dubbo-go 项目还只是构建初期,没有什么用户落地的经验。我也是一边参与社区开发,一边编码公司内部网关项目。在解决了一堆 hessain 序列化和 zookeeper 注册中心的问题后,项目最终跑通了闭环。但是,作为一个核心应用,跑通闭环离上生产环境还有很长的路要走,特别是使用了当时稳定性待测试的新框架。整个测试加上功能补全,整整花费了一个季度的时间,直到项目趋于稳定,压测效果也良好。单台网关机器( 2C 8G )全链路模拟真实环境压测达到 2000 QPS。由于引入了比较重的业务逻辑(单个请求平均调用 3 个 dubbo 接口),对于这个压测结果,是符合甚至超出预期的。

      总结了一些 dubbo-go 参数配置调优的经验,主要是一些网络相关配置。

      大家在跑 demo 时,应该会看到配置文件最后有一堆配置,但如果对 dubbo-go 底层网络模型不熟悉,就很难理解这些配置的含义。目前 dubbo-go 网络层以 getty 为底层框架,实现读写分离和协程池管理。getty 对外暴露 session 的概念,session 提供一系列网络层方法注入的实现,因为本文不是源码解析文档,在这里不过多论述。读者可以简单的认为 dubbo-go 维护了一个 getty session池,session 又维护了一个 TCP 连接池。对于每个连接,getty 会有读协程和写协程伴生,做到读写分离。这里我尽量用通俗的注释帮大家梳理下对性能影响较大的几个配置含义:

      protocol_conf:
        # 这里是协议独立的配置,在dubbo协议下,大多数配置即为getty session相关的配置。
        dubbo:
            # 一个session会始终保证connection_number个tcp连接个数,默认是16,
          # 但这里建议大家配置相对小的值,一般系统不需要如此多的连接个数。
          # 每隔reconnect_interval时间,检查连接个数,如果小于connection_number,
          # 就建立连接。填0或不填都为默认值300ms
          reconnect_interval: 0
          connection_number: 2
          # 客户端发送心跳的间隔
          heartbeat_period: "30s"
          # OnCron时session的超时时间,超过session_timeout无返回就关闭session
          session_timeout: "30s"
          # 每一个dubbo interface的客户端,会维护一个最大值为pool_size大小的session池。
          # 每次请求从session池中select一个。所以真实的tcp数量是session数量*connection_number,
          # 而pool_size是session数量的最大值。测试总结下来一般程序4个tcp连接足以。
          pool_size: 4
          # session保活超时时间,也就是超过session_timeout时间没有使用该session,就会关闭该session
          pool_ttl: 600
          # 处理返回值的协程池大小
          gr_pool_size: 1200
          # 读数据和协程池中的缓冲队列长度,目前已经废弃。不使用缓冲队列
          queue_len: 64
          queue_number: 60
          getty_session_param:
            compress_encoding: false
            tcp_no_delay: true
            tcp_keep_alive: true
            keep_alive_period: "120s"
            tcp_r_buf_size: 262144
            tcp_w_buf_size: 65536
            pkg_wq_size: 512
            tcp_read_timeout: "1s"  # 每次读包的超时时间
            tcp_write_timeout: "5s" # 每次写包的超时时间
            wait_timeout: "1s" 
            max_msg_len: 102400     # 最大数据传输长度
            session_name: "client"

      dubbo-go 快速接入

      前文已经展示过 dubbo-go 在涂鸦智能的实践成果,接下来介绍快速接入 dubbo-go 的方式。

      第一步:hello world

      dubbo-go 使用范例目前和 dubbo 一致,放置在 apache/dubbo-samples 项目中。在 dubbo-sample/golang 目录下,用户可以选择自己感兴趣的 feature 目录,快速测试代码效果。

      tree dubbo-samples/golang -L 1
      dubbo-samples/golang
      ├── README.md
      ├── async
      ├── ci.sh
      ├── configcenter
      ├── direct
      ├── filter
      ├── general
      ├── generic
      ├── go.mod
      ├── go.sum
      ├── helloworld
      ├── multi_registry
      └── registry

      我们以 hello world 为例,按照 dubbo-samples/golang/README.md 中的步骤,分别启动 server 和 client 。可以尝试 golang 调用 java 、 java 调用 golang 、golang 调用 golang 、java 调用 java。dubbo-go 在协议上支持和 dubbo 互通。

      我们以启动 go-server 为例,注册中心默认使用 zookeeper 。首先确认本地的 zookeeper 是否运行正常。然后执行以下命令,紧接着你就可以看到你的服务正常启动的日志了。

      export ARCH=mac
      export ENV=dev
      cd dubbo-samples/golang/helloworld/dubbo/go-server
      sh ./assembly/$ARCH/$ENV.sh
      cd ./target/darwin/user_info_server-2.6.0-20200608-1056-dev/
      sh ./bin/load.sh start

      第二步:在项目中使用 dubbo-go

      上面,我们通过社区维护的测试代码和启动脚本将用例跑了起来。接下来,我们需要在自己的代码中嵌入 dubbo-go 框架。很多朋友往往是在这一步遇到问题,这里我整理的一些常见问题,希望能帮到大家。

      1)环境变量

      目前 dubbo-go 有 3 个环境变量需要配置:

      • CONF_CONSUMER_FILE_PATH:Consumer 端配置文件路径,使用 consumer 时必需;
      • CONF_PROVIDER_FILE_PATH:Provider 端配置文件路径,使用 provider 时必需;
      • APP_LOG_CONF_FILE:Log 日志文件路径,必需;
      • CONF_ROUTER_FILE_PATH:File Router 规则配置文件路径,使用 File Router 时需要。

      2)代码注意点

      • 注入服务 : 检查是否执行以下代码
      # 客户端
      func init() {
        config.SetConsumerService(userProvider)
      }
      # 服务端
      func init() {
        config.SetProviderService(new(UserProvider))
      }
      • 注入序列化描述 :检查是否执行以下代码
      hessian.RegisterJavaEnum(Gender(MAN))
        hessian.RegisterJavaEnum(Gender(WOMAN))
        hessian.RegisterPOJO(&User{})

      3)正确理解配置文件

      • references / services 下的 key ,如下面例子的 "UserProvider" 需要和服务 Reference() 返回值保持一致,此为标识改接口的 key。
         
      references:
      "UserProvider":
        registry: "hangzhouzk"
        protocol : "dubbo"
        interface : "com.ikurento.user.UserProvider"
        cluster: "failover"
        methods :
        - name: "GetUser"
          retries: 3
      • 注册中心如果只有一个注册中心集群,只需配置一个。多个 IP 用逗号隔开,如下:
      registries :
      "hangzhouzk":
        protocol: "zookeeper"
        timeout    : "3s"
        address: "172.16.120.181:2181,172.16.120.182:2181"
        username: ""
        password: ""

      4)java 和 go 的问题

      go 和 java 交互的大小写 :golang 为了适配 java 的驼峰格式,在调用 java 服务时,会自动将 method 和属性首字母变成小写。很多同学故意将 java 代码写成适配 golang 的参数定义,将首字母大写,最后反而无法序列化匹配。

      第三步:拓展功能

      dubbo-go 和 dubbo 都提供了非常丰富的拓展机制。可以实现自定义模块代替 dubbo-go 默认模块,或者新增某些功能。比如实现 Cluster、Filter 、Router 等来适配业务的需求。这些注入方法暴露在 dubbo-go/common/extension 中,允许用户调用及配置。

      阿里巴巴编程之夏第二期正在火热报名中!

      2020 年 5 月 25 日,阿里巴巴编程之夏(Alibaba Summer of Code,以下简称 ASoC )第二期正式上线,项目规模再度升级,来自开源社区的 Apache Dubbo、Apache RocketMQ、Dragonfly、Nacos 等明星开源项目多达 20 个;导师阵容配置豪华,来自阿里巴巴集团的技术专家、开源社区核心成员、Apache 孵化器导师等多达 32 位;领域涉及微服务、容器、AI 等多个热点方向,旨在联合开源社区打造诚意满满、公平公正的开源实习平台,以阿里巴巴开源技术力量为“推手”,让中国开源社区和开发者精英受到世界范围内的认可。

      点击了解详情:https://developer.aliyun.com/topic/summerofcode2020

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      开放下载!《ECS运维指南 之 Linux系统诊断》 | 开发者必读(173期)-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800

      最炫的技术新知、最热门的大咖公开课、最有趣的开发者活动、最实用的工具干货,就在《开发者必读》!

      每日集成开发者社区精品内容,你身边的技术资讯管家。

      每日干货推荐

      开放下载!《ECS运维指南 之 Linux系统诊断》>>>

      经过深入思考、用心、用技术写作,快速提高Linux运维工程师的工作效率,是云运维工程师不可错过的匠心之作。

      独家下载!《15分钟开发视觉AI应用》>>>

      零门槛搞定人脸识别、口罩识别、图片内容安全应用,教程在手,从入门到精通一站掌握!

      再招5000人!2020阿里云峰会透露了哪些核心信息?(附14个资料下载)>>>

      在 6 月 9 日 2020 阿里云线上峰会上,张建锋表达了哪两个核心观点呢?阿里云再生长三大方向是什么呢?有哪些方案发布?又有哪些重磅技术发布?本文为你细细道来,持续更新中~

      更多精彩文章

      揭秘 SIGCOMM 20‘ 论文:阿里云网络洛神 VTrace 系统>>>

      VTrace 是一个解决云网络持续性丢包问题的自动化诊断系统,核心思想是“任务-匹配-染色-采集-分析”,结合大数据技术,旨在实时快速的自动分析出云网络端到端的流量拓扑路径,并给出准确的问题原因和解决方案,让网络运维不再需要那么“专业”,那么“复杂”。

      一张图讲清楚淘宝直播背后技术( 赠送多媒体前端手册)>>>

      2020年,直播带货火爆全网。想一探淘宝直播背后的前端技术?本文将带你进入淘宝直播前端技术的世界。 对于大多数前端工程师来说,音视频技术是一个比较少涉足的领域,本文涵盖了流媒体技术中的文本、图形、图像、音频和视频多种理论知识,涉及到播放器、web媒体技术、主流框架等介绍,只需要花上一点点时间,你将进入前端多媒体的领域。

      如何画好一张架构图?>>>

      架构图是什么?为什么要画架构图?如何画?有哪些方法?本文从架构的定义说起,分享阿里文娱高级技术专家箫逸关于画架构图多年的经验总结,并对抽象这一概念进行了深入地讨论。较长,同学们可收藏后再看。

      精品公开课

      快速交付云原生应用的 3 种发布策略详解>>>

      交付策略决定交付结果,通常一次交付主要包含网络的变更和服务载体的变更,如何保证决定了交付过程中的可靠性、连续性、稳定性?本次课程将介绍 3 种常用的应用交付策略。

      云原生精彩故事:容器服务还能变成抗疫助学技术实践?容器安全该如何探索?>>>

      本期阿里云内外专家,跟您聊聊云原生那些事儿,业内首个全托管Istio兼容的服务网格产品ASM。疫情下容器安全的探索,如何在96小时完成业务部署。

      【阿里的K8s测试环境开源工具箱】第一期:阿里的测试环境管理实践概述>>>

      本次分享将介绍一种在阿里广泛使用的测试环境管理模式以及它的Kubernetes开源版本,希望对采用云原生技术栈的IT企业和开发者有所启发。


      每日集成开发者社区精品内容,请持续关注开发者必读

      ]]>
      常见系统模块-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 本文来自于千锋教育在阿里云开发者社区学习中心上线课程《Python入门2020最新大课》,主讲人姜伟。

      Sys模块的使用

      该模块提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数。

      sys.exit(code)
      让程序以指定的退出码结束。

      import sys
      
      print('hello world')
      sys.exit()    # 程序退出,和内置函数exit功能一致
      print('呵呵呵呵')

      执行结果:
      image.png

      sys.exit(100) 

      image.png
      sys.path
      模块的查找路径。

      print(sys.path)

      执行结果:
      image.png
      结果是一个列表,表示查找模块的路径。

      sys.stdin
      标准输入。可以通过它来获取用户的输入。

      sys.stdout
      标准输出。可以通过修改它来改变默认输出位置

      sys.stderr
      错误输出。可以通过修改它来改变错误输出的默认位置

      sys.stderr
      
      1 / 0

      执行结果:
      image.png
      sys.stdoutsys.stderr默认都是在控制台。

      sys.argv
      传递给Python脚本的命令行参数列表。

      Math模块

      math模块保存了数学计算相关的方法,可以很方便的实现数学运算。

      math.factorial(6)
      计算阶乘

      import math
      print(math.factorial(6))

      执行结果:
      image.png

      math.floor(12.98)
      向下取整

      import math
      print(math.floor(12.98))

      执行结果:
      image.png

      math.ceil(15.0001)
      向上取整

      import math
      print(math.ceil(15.0001))

      执行结果:
      image.png

      math.pow(2, 10)
      2的10次方,幂运算

      import math
      print(math.pow(2, 10))

      执行结果:
      image.png

      #2 ** 10 / pow(2,10) / math.pow(2,10)
      都可以计算幂运算,但是有一些细微的差别。

      round()
      不是math中的,属于内置函数,实现四舍五入到指定位数。

      math.pi
      π的值,约等于 3.141592653589793

      math.sin(math.pi / 6)
      正弦值,弧度计算,π=180°

      import math
      print(math.sin(math.pi / 6))

      执行结果:
      image.png

      math.cos(math.pi / 3)
      余弦值

      import math
      print(math.cos(math.pi / 3)) 

      执行结果:
      image.png

      math.tan(math.pi / 2)
      正切值

      import math
      print(math.tan(math.pi / 2)) 

      执行结果:
      image.png

      math.fabs(-100)
      取绝对值

      import math
      print(math.fabs(-100))

      执行结果:
      image.png

      random模块

      random 模块主要用于生成随机数或者从一个列表里随机获取数据。

      randint(a,b)
      生成[a,b]的随机整数,等价于randrange(a, b+1)。

      import random
      print(random.randint(2, 9)) 

      执行结果:
      image.png

      random()
      生成 [0,1)的随机浮点数。

      import random
      print(random.random())

      执行结果:
      image.png

      randrange(a, b)
      生成[a,b)的随机整数。

      import random
      print(random.randrange(20, 30)) 

      执行结果:
      image.png

      choice()
      从可迭代对象里随机取出一个元素

      import random
      print(random.choice(['zhangsan', 'lisi', 'jack', 'henry', 'tony'])) 

      执行结果:
      image.png

      sample()
      从可迭代对象里随机取出指定个数的元素

      import random
      print(random.sample(['zhangsan', 'lisi', 'jack', 'henry', 'tony'], 2)) 

      执行结果:
      image.png

      uniform(a,b)
      生成[a,b]的随机浮点数

      import random
      print(random.uniform(20, 30)) 

      执行结果:
      image.png

      Datetime模块

      datetime模块主要用来显示日期时间,这里主要涉及 date 类,用来显示日期; time 类,用来显示时间; datetime 类,用来显示日期时间; timedelta 类用来计算时间。

      import datetime as dt
      print(dt.date(2020, 1, 1)) # 创建一个日期
      print(dt.time(18, 23, 45)) # 创建一个时间
      print(dt.datetime.now()) # 获取当前的日期时间
      print(dt.datetime.now() + dt.timedelta(3)) # 计算三天以后的日期时间

      执行结果:
      image.png
      涉及四个类:date/time/datetime/timedelta。
      我们接触过的类:内置类
      list、tuple、int、str。

      time模块

      除了使用datetime模块里的time类以外,Python还单独提供了另一个time模块,用来操作时间。time模块不仅可以用来显示时间,还可以控制程序,让程序暂停(使用sleep函数)。

      import time
      print(time.time()) # 获取从1970-01-01 00:00:00 UTC 到现在时间的秒数
      print(time.strftime("%Y-%m-%d %H:%M:%S")) # 按照指定格式输出时间
      print(time.asctime()) #Mon Apr 15 20:03:23 2019
      print(time.ctime()) # Mon Apr 15 20:03:23 2019
      print('hello')
      print(time.sleep(10)) # 让线程暂停10秒钟
      print('world')

      执行结果:
      image.png
      ctime()给定一个秒数,生成一个时间。传递一个时间戳。
      asctime()传递的是一个元组。
      image.png

      calendar模块

      calendar模块用来显示一个日历。

      c = calendar.calendar(2019) # 生成2019年的日历,并且以周日为起始日期码
      print(c) #打印2019年日历
      
      calendar.setfirstweekday(calendar.SUNDAY) # 设置每周起始日期码。周一到周日分别对应 0 ~ 6
      
      calendar.firstweekday()# 返回当前每周起始日期的设置。默认情况下,首次载入calendar模块时返回0,即星期一。
      
      print(calendar.isleap(2000)) # True.闰年返回True,否则返回False
      
      count = calendar.leapdays(1996,2010) # 获取1996年到2010年一共有多少个闰年
      print(count)
      
      print(calendar.month(2019, 3)) # 打印2019年3月的日历

      执行结果:
      image.png
      image.png
      image.png
      image.png
      image.png
      image.png

      hashlib模块

      hashlib是一个提供字符加密功能的模块,包含MD5SHA的加密算法,支持md5,sha1, sha224,sha256, sha384, sha512等算法。 该模块用户登录面广泛,文本加密也很见。

      加密方式:单向加密:只有加密的过程,不能解密md5/sha、
      对称加密、非对称加密rsa

      需要将加密的内容转化为二进制

      import hashlib
      
      x = hashlib.md5() # 生成一个md5对象
      x.update('abc'.encode('utf8'))
      print(x.hexdigest())

      执行结果:
      image.png

      import hashlib
      
      h1 = hashlib.sha1('123456'.encode())
      print(h1.hexdigest())
      h2 = hashlib.sha224('123456'.encode())  #  224位 一个十六进制占4位
      print(h2.hexdigest())
      h3 = hashlib.sha256('123456'.encode())
      print(h3.hexdigest())
      h4 = hashlib.sha384('123456'.encode())
      print(h4.hexdigest())

      执行结果:
      image.png

      hmac模块

      HMAC算法也是一种单项加密算法,并且它是基于上面各种哈希算法/散列算法的,只是它可以在运算过程中使用一个密钥来增强安全性。hmac模块实现了HAMC算法,提供了相应的函数和方法,且与hashlib提供的api基本一致。

      hmac加密可以指定密钥

      import hmac
      
      h = hmac.new('h'.encode(),'你好'.encode())
      result = h.hexdigest()
      print(result) # 获取加密后的结果

      执行结果:
      image.png

      copy模块

      copy模块里有copy和deepcopy两个函数,分别用来对数据进行深复制和浅复制。

      import copy
      
      nums = [1, 5, 3, 8, [100, 200, 300, 400], 6, 7]
      nums1 = copy.copy(nums) # 对nums列表进行浅复制
      nums2 = copy.deepcopy(nums) # 对nums列表进行深复制
      
      print(nums1)
      print(nums2)

      执行结果:
      image.png

      uuid模块

      UUID是128位的全局唯一标识符,通常由32字节的字母串表示,它可以保证时间和空间的唯一性,也称为GUID。通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生产的ID的唯一性。随机生成字符串,可以当成token使用,当成用户账号使用,当成订单号使用。

      方法 作用
      uuid.uuid1() 基于MAC地址,时间戳,随机数来生成唯一的uuid,可以保证全球范围内的唯一性。
      uuid.uuid2() 算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。不过需要注意的是python中没有基于DCE的算法,所以python的uuid模块中没有uuid2这个方法。
      uuid.uuid3(namespace,name) 通过计算一个命名空间和名字的md5散列列值来给出一个uuid,所以可以保证命名空间中的不同名字具有不同的uuid,但是相同的名字就是相同的uuid了。namespace并不是一个自己手动指定的字符串或其他量,而是在uuid模块中本身给出的一些值。比如uuid.NAMESPACEDNS,uuid.NAMESPACEOID,uuid.NAMESPACE_OID这些值。这些值本身也是UUID对象,根据一定的规则计算得出。
      uuid.uuid4() 通过伪随机数得到uuid,是有一定概率重复的
      uuid.uuid5(namespace,name) 和uuid3基本相同,只不过采用的散列算法是sha1

      一般而言,在对uuid的需求不是很复杂的时候,uuid1或者uuid4方法就已经够用了,使用方法如下:

      import uuid
      
      print(uuid.uuid1()) # 根据时间戳和机器码生成uuid,可以保证全球唯一   32个长度,每一个字符有16个选择,16**32
      
      # 使用命名空间和字符串生成uuid.
      # 注意以下两点:
      # 1. 命名空间不是随意输入的字符串,它也是一个uuid类型的数据
      # 2. 相同的命名空间和相同的字符串,生成的uuid是一样的
      print(uuid.uuid3(uuid.NAMESPACE_DNS, 'hello')) # 生成固定的uuid
      print(uuid.uuid5(uuid.NAMESPACE_OID, 'hello')) # 生成固定的uuid
      
      print(uuid.uuid4()) # 随机生成uuid,可能会有重复

      执行结果:
      image.png

      配套视频

      ]]>
      K8s 资源全汇总 | K8s 大咖带你 31 堂课从零入门 K8s-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 默认文件1587981156953.jpg

      关注“阿里巴巴云原生”公众号,后台回复“入门”,即可下载课程全部 PPT 合集!

      从 2019 年,云原生技术开始大规模普及,以“云”为核心的软件研发思想,正逐步成为所有开发者的默认选项,像 Kubernetes 等云原生技术正在成为技术人员的必修课。这种背景下,“会 Kubernetes”已经远远不够了,“懂 Kubernetes”、“会云原生架构”的重要性正日益凸显出来,这也是为什么大家都要学习和投资云原生技术的重要原因。

      【课程介绍】

      本课程是由 CNCF 官方与阿里巴巴强强联合,共同推出的以“云原生技术体系”为核心、以“技术解读”和“实践落地”并重的系列技术公开课。课程全程免费且无需注册,为您带来:

      • 完善的知识体系,打造属于自己的云原生技能树
      • 理解云原生技术背后的思想与本质
      • 与知识体系相辅相成的动手实践
      • 一线技术团队云原生技术最佳实践

      【讲师阵容】

      来自全球“云原生”技术社区的亲历者和领军人物,如作为 CNCF 全球 TOC 之一的李响及 CNCF 官方大使张磊,齐聚“课堂”为每一位中国开发者讲解和剖析关于“云原生”的方方面面,旨在让广大中国开发者可以近距离聆听世界级技术专家解析云原生技术,让“云原生”技术真正触手可及。

      【资源汇总】

      • 课时1 《Kubernetes 入门必备云原生发展简史》

      图文版课程链接:https://developer.aliyun.com/article/717581
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13076

      • 课时2 《从零开始入门 K8s | 详解 K8s 容器基本概念》

      图文版课程链接:https://developer.aliyun.com/article/718433
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13082

      • 课时3 《从零开始入门 K8s | 阿里技术专家详解 K8s 核心概念》

      图文版课程链接:https://developer.aliyun.com/article/718798
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13078

      • 课时4 《从零入门 K8s | 人人都能看懂 Pod 与容器设计模式》

      图文版课程链接:https://mp.weixin.qq.com/s/OW7zvGhPgGAnBuo4A_SXFw
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13079

      • 课时5 《从零开始入门 K8s | K8s 的应用编排与管理》

      图文版课程链接:https://mp.weixin.qq.com/s/T1l5ebHqo0GUFfbfBnd-tQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13080

      • 课时6 《从零开始入门 K8s | 应用编排与管理》

      图文版课程链接:https://developer.aliyun.com/article/719455
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13081

      • 课时7 《从零开始入门 K8s | 应用编排与管理:Job & DaemonSet》

      图文版课程链接:https://mp.weixin.qq.com/s/XdhdAxaLPg7ZUS5WonXhXw
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13083

      • 课时8 《从零开始入门 K8s | 如何实现应用配置管理?》

      图文版课程链接:https://mp.weixin.qq.com/s/8r-_Ekje__GVHsKLfJ-66A
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13084

      • 课时9 《从零开始入门 K8s | 应用存储和持久化数据卷:核心知识》

      图文版课程链接:https://developer.aliyun.com/article/720251
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13085

      • 课时10 《从零开始入门 K8s | 应用存储和持久化数据卷:存储快照与拓扑调度》

      图文版课程链接:https://mp.weixin.qq.com/s/enoy5KRd2sxpZFZ6f06ZiQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_15457

      • 课时11 《从零开始入门 K8s | 可观测性:你的应用健康吗?》

      图文版课程链接:https://mp.weixin.qq.com/s/L1KT0VISxlGYZXGtLzSwBA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13086

      • 课时12 《从零开始入门 K8s | 可观测性:监控与日志》

      图文版课程链接:https://mp.weixin.qq.com/s/5z4IhCJhCfwbSZne93VEsQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_15541

      • 课时13 《从零开始入门 K8s | Kubernetes 网络概念及策略控制》

      图文版课程链接:https://mp.weixin.qq.com/s/kjOAlKTwaMZzVOiSuJE6fQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13087

      • 课时14 《从零开始入门 | Kubernetes 中的服务发现与负载均衡》

      图文版课程链接:https://mp.weixin.qq.com/s/klc0GYAcTthPdUaF-O7izQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_15658

      • 课时15 《从零开始入门 K8s | 深入剖析 Linux 容器》

      图文版课程链接:https://mp.weixin.qq.com/s/r3BCE46iAMn4gqbY3lS6TA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13088

      • 特别篇 《Helm 从入门到实践 | 从 0 开始制作一个 Helm Charts》

      图文版课程链接:https://mp.weixin.qq.com/s/uWdPB4LvmMmvUX2ioKwbww
      视频版课程链接:https://edu.aliyun.com/lesson_1651_16513

      • 课时16 《从零开始入门 K8s | 手把手带你理解 etcd》

      图文版课程链接:https://mp.weixin.qq.com/s/qjLevhAn43d6jWQR_WTMJw
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13089

      • 课时17 《从零开始入门 K8s | etcd 性能优化实践》

      图文版课程链接:https://mp.weixin.qq.com/s/lD2b-DZyvRJ3qWqmlvHpxg
      视频版课程链接:https://edu.aliyun.com/lesson_1651_16861

      • 课时18 《从零开始入门 K8s | Kubernetes 调度和资源管理》

      图文版课程链接:https://mp.weixin.qq.com/s/yD7vmIq9iCGrwAUKHs9WqA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13090

      • 课时19 《从零开始入门 K8s | 调度器的调度流程和算法介绍》

      图文版课程链接:https://mp.weixin.qq.com/s/1ntzs0__3FnLJRE9MDDCnQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_17018

      • 课时20 《从零开始入门 K8s | GPU 管理和 Device Plugin 工作机制》

      图文版课程链接:https://mp.weixin.qq.com/s/-rUaFgTbI-GI-LmJTkferQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13091

      • 课时21 《从零开始入门 K8s | Kubernetes 存储架构及插件使用》

      图文版课程链接:https://mp.weixin.qq.com/s/QWLGkpqpMdsY1w6npZj-yQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13092

      • 课时22 《从零开始入门 K8s | 有状态应用编排 - StatefulSet》

      图文版课程链接:https://mp.weixin.qq.com/s/oTNv8gop93ljEUGhfKzNrw
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13093

      • 课时23 《从零开始入门 K8s | Kubernetes API 编程范式》

      图文版课程链接:https://mp.weixin.qq.com/s/uBCNTLCllE0tl70oLhLufg
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13094

      • 课时24 《从零开始入门 K8s | Kubernetes API 编程利器:Operator 和 Operator Framework》

      图文版课程链接:https://mp.weixin.qq.com/s/SdWp1A6zDp7inLzG5VSE4Q
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13095

      • 课时25 《从零开始入门 K8s | Kubernetes 网络模型进阶》

      图文版课程链接:https://mp.weixin.qq.com/s/Jm8VynGd506wN5-yiLHzdg
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13096

      • 课时26 《从零开始入门 K8s | 理解 CNI 和 CNI 插件》

      图文版课程链接:https://mp.weixin.qq.com/s/sGTEp9m8PC2zhlEgcnqtZA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13097

      • 课时27 《从零开始入门 K8s | K8s 安全之访问控制》

      图文版课程链接:https://mp.weixin.qq.com/s/nPErpcghHih5-dGPQkStJA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13099

      • 课时28 《从零开始入门 K8s | 理解容器运行时接口 CRI》

      图文版课程链接:https://mp.weixin.qq.com/s/aexS9HmlxCpmY7gn3MIFsA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_17238

      • 课时29 《从零开始入门 K8s | Kata Containers 创始人带你入门安全容器技术》

      图文版课程链接:https://mp.weixin.qq.com/s/w2SkC6TuSBqurvAae0RAUA
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13100

      • 课时30 《从零开始入门 K8s | 理解 RuntimeClass 与使用多容器运行时》

      图文版课程链接:https://mp.weixin.qq.com/s/JQ2NNHmpUrVUqX6BIyqUmQ
      视频版课程链接:https://edu.aliyun.com/lesson_1651_13101

      关注“阿里巴巴云原生”公众号,后台回复“入门”,即可下载课程全部 PPT 合集!

      阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

      ]]>
      进制转换 | 手把手教你入门Python之十七-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 本文来自于千锋教育在阿里云开发者社区学习中心上线课程《Python入门2020最新大课》,主讲人姜伟。

      进制

      现代的计算机和依赖计算机的设备里都用到二进制(即0和1)来保存和表示数据,一个二进制表示一个比特 (Bit)。
      在⼆进制的基础上,计算机还支持八进制和十六进制这两种进制。
      除了计算机里的进制以外,我们生活中经常用到的是十进制。
      Python语⾔支持二进制、八进制、十六进制以及十进制的数字表示。

      a = 12 # 默认数字是十进制 print(a + 1)
      b = 0b11101   # 以0b开头的数字是二进制 print(b + 1)
      c = 0o12  # 以 0o 开头的数字是八进制 print(c + 1)
      d = 0x1A  # 以 0x 开头的数字是十六进制 print(d + 1)
      

      image.png
      image.png

      进制转换

      不同进制的数字之间可以通过一定的计算规则相互转换。
      十进制转制为二进制:
      image.png
      image.png

      二进制转制为八进制十六进制以及十进制:
      image.png

      image.png

      ]]>
      使用pip管理第三方包-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 本文来自于千锋教育在阿里云开发者社区学习中心上线课程《Python入门2020最新大课》,主讲人姜伟。

      pip命令的使用

      在安装Python时,同时还会安装pip软件,它是Python的包管理工具,可以用来查找、下载、安装和卸载Python的第三方资源包。

      配置pip

      可以直接在终端中输入pip命令,如果出错,可能会有两个原因:
      1、 pip安装成功以后没有正确配置
      2、 安装Python时,没有自动安装pip(很少见)

      和运行Python命令一样,如果想要运行 pip 命令同样也需要将pip命令的安装目录添加到环境变量中。
      image.png
      image.png

      安装pip

      如果在Python安装对应的目录中,没有发现pip.exe文件,可能是因为在安装Python时未自动安装pip,建议将Python卸载,然后在重新安装Python时选择Install Now 使用默认方式安装Python。
      image.png

      管理第三方包

      对第三方包的管理主要包含查找、安装和卸载三个部分的操作。

      安装

      使用 pip install <包名> 命令可以安装指定的第三方资源包。

      pip install ipython # 安装ipython包

      使用 install 命令下载第三方资源包时,默认是从 pythonhosted下载,由于各种原因,在国内下载速度相对来说比较慢,在某些时候甚至会出现连接超时的情况,我们可以使用国内镜像来提高下载速度。

      临时修改

      如果只是想临时修改某个第三方资源包的下载地址,在第三方包名后面添加 -i 参数,再指定下载路径即可,格式为pip install <包名> -i <国内镜像路径>

      pip install ipython -i https://pypi.douban.com/simple

      永久修改

      除了临时修改pip的下载源以外,我们还能永久改变pip的默认下载路径。
      在当前用户目录下创建一个pip的文件夹,然后再在文件夹里创建pip.ini文件并输入以下内容:

      [global]
      index-url=https://pypi.douban.com/simple
      [install]
      trusted-host=pypi.douban.com

      常见国内镜像

      卸载

      使⽤用 pip install <包名> 命令可以用来卸载指定的第三方资源包。

      pip uninstall ipython # 卸载ipython包

      查找

      使用pip list 或者 pip freeze 命令可以来管理第三方资源包。这两个命令的功能一致,都是用来显示当前环境里已经安装的包,区别在于pip list 会列出所有的包,包括一些无法uninstall的包;而pip freeze 只会列出我们安装的第三方包。

      总结

      开发中,我们通常会使用很多第三方的资源包,我们在将程序部署到服务器的时候,不仅要把代码上传到服务器,同时还需要把代码里用到的第三方资源包告诉服务器。那么这里就有两个问题:
      1、当我们电脑上运行很多个项目,每个项目使用的第三方资源包又不一致时,怎样将代码和它使用到的第三方资源包放在一起呢?
      答:虚拟环境
      2、怎样将自己代码使用到的第三方资源包告诉给服务器?

      1、 使用 pip freeze > requires.txt 命令,将代码里使用到的第三方资源包以及版本号写入到requirements.txt 文件,在部署时,同时将 requirements.txt 文件上传到服务器。

      pip freeze > file_name 将安装的模块名和版本号重定向输出到指定的文件。

      2、 服务器在拿到代码以后,首先运行 pip install -r requirements.txt 命令,将文件里列出的所有第三方框架先安装到服务器,然后才能运行代码。

      pip install -r file_name 读取文件里的模块名和版本号并安装

      使用pycharm管理第三方包

      除了使用pip 命令管理第三方资源包以外,我们还能使用pycharm来对第三方包进行管理。
      image.png

      配套视频

      ]]>
      结合 5G和边缘计算,优酷如何做云渲染?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 作者| 阿里文娱高级技术专家 伊耆

      当5G来了,视频还是平面的影像吗,只能静静观看吗?一定不是!现在,你可以像玩游戏一样,参与到视频内容当中,还能体验新的播放形式,比如发AI弹幕、猜剧情、横竖屏随意旋转,立体的观看进球一瞬间,看到屏幕之外的更大画面等等。这背后的技术是如何实现的,未来有哪些新交互方向?

      在GMIC智慧文娱技术专场上,阿里文娱高级技术专家伊耆分享了如何利用终端设备的交互特性,结合内容和算法,所实现的新观影模式的探索。同时结合5G网络和边缘计算所做的云渲染技术预研。主要分为四部分:
      一是视频和游戏的共性和差异,如何看待两者?
      二是视频场景结合内容、算法探索播放新交互模式
      三是结合 5G、边缘计算和立体视觉的云渲染技术
      四是未来的思考和总结

      一、为什么做播放交互探索?视频和游戏的共性和差异

      为什么要做新交互?其实用户在文娱消费体验上,尤其在视觉体验上,主要集中在两个领域,一是视频,一是游戏。我们在思考切入点时,更多是关注两者之间的共性和差异,寻找结合点。
      首先回想一下,你在玩游戏是一种什么样的体验?网上有很多类型的游戏,休闲类、益智类、竞技类等等,我们可以发现游戏的特点是交互性越强,竞技属性越强;交互属性越弱,休闲体验越强。视频的本质相同,在一个纯被动观看的过程中,内容本身会带来感官刺激,但更多体现在休闲上。但随着交互属性的加入,比如当视频引入VR/AR等互动后,其形态也更趋于游戏化,更像是一种休闲类的游戏。也就是视频的“内容属性”与游戏的“交互属性”结合,最终它可能就变成一个像游戏化的视频了,用户会获得比较强的沉浸式的感觉。

      二、视频场景结合内容、算法探索播放新交互模式


      image.png

      参考优酷在互动剧的尝试,我们在播放和交互领域的结合也做了非常多的探索。先看2个视频。视频1是常规的旋转,体验还算顺滑;视频2是加入算法后的策略,在旋转过程中,画面始终是平稳的,甚至用户在横移手机时,可以在屏幕中看到更多的画面,这也是初步尝试。
      以旋转的视频为例,形式上看似简单,但它背后也有很多技术点:
      1、旋转手机时,不丢失画面中心。我们看视频时,视线往往会聚焦在一个中心人物,或是一个场景中心。当手机旋转,自然也不希望丢失视觉的中心点。所以需要一套算法去识别观影中心点。在此基础上,通过服务链路去打通,将算法和视频画面联合下发到端侧,并将数据和画面进行绑定,同时在用户旋转手机时,通过对手势的监测选择对应的画面中心点,并进行画面的放大、缩小或平移。
      2、基于原始的大量数据样本,做算法模型训练,得到对于视频画面区域热度算法的模型。由于视频是一个连续过程,我们需要对镜头的切换做平滑处理,结合算法生成一个原始的算法数据。

      image.png

      3、将算法数据和视频内容做关系绑定,并下发到端侧。这样在端侧就同时具备算法的数据和实际播放的视频数据。在播放进程中,我们需要获取旋转-陀螺仪传感器的输入,也会利用降噪算法过滤躁点,根据用户的旋转角度,结合当前视频画面,将算法数据和画面本身绑定,找到画面中心点,做相应处理,最终渲染到屏幕上。
      以上是大致实现思路,在落地过程中,我们也面临不少挑战,最突出的是算法与传统图像处理算法不同。普通的图像处理多是基于单张图片,而视频本身是多帧的,而且每个视频帧间是连续性的。同时在识别过程中,尤其对于运动场景、切换镜头的场景,普通算法的识别焦点是存在偏差的,甚至识别不到,所以我们需要新的处理。
      image.png

      在算法设计上,采用镜头分割方式,区分不同的场景镜头,然后对于每个镜头,我们认为是画面是连续的。这部分,我们结合现有成熟算法,融入自己的技术探索。
      首先,在看画面时,人眼睛会聚焦在人脸、人体,这些点的区域热度是比较高的,将些场景样本作为模型训练数据,同时视频本身还有部分字幕,也需要去除、识别和检测的处理。综合这一系列的检测内容,最终把一帧帧画面看成一个连续的轨迹,做聚类,形成一个角色或者是一个热度点的轨迹;集合多个镜头,形成一个视频区域热度算法的数据,然后下发到端侧。
      其次,有了算法数据,在端侧更多是如何处理端上传感器,处理算法数据和视频之间的同步问题。
      以上是我们现阶段的尝试,同步也在做其他尝试,在不远的未来也会逐步上线,大家很快就体验到。

      三、基于 5G 的云渲染

      在现有场景上,算法数据是基于原始视频进行识别,由于中间需要预生产过程,这就局限了它更多是在点播场景中。
      如果不做预生产,而在端侧进行,则会产生识别的速度不够,效率底的问题,以及在不一样的交互时,处于实时性的诉求,本身对端侧算力是非常大挑战。结合5G的发展,我们设计出云渲染方案。首先看两个视频:

      视频3在电脑上,可以认为它是一个云端主机,在云端是一个高清画质。而在手机端,用户真正看到画面,只是云端画面的一部分。为什么这样设计?
      视频4是6DoF视频,用户可以通过手势旋转,从各角度看到不一样的视角。
      6DoF视频的本质是,用户看到的某些角度的视频,其实是很多角度拼合的画面,用户在选择某一角度时,我们经过截取,提取其中两个画面,通过算法虚拟生成,一个用户观看角度的这么一个画面,然后下发到端侧。
      6DoF视频的某一帧,真实画面本身是非常大的画面,8k甚至11k。用户端看到是其中一部分,720p或1080p,其对应的VR场景也类似。
      挑战是什么?用户观看VR全景视频时,本质是4k甚至8k视频,但用户在每一个视角上看到的点,可能只有720p甚至更低。想看更高清的画质,就必须提升画面的大小。如果我们希望要看到4k画面,原始画面要达到8k,甚至更高。
      8K画面下发到端侧是无法解决问题的。一是芯片的限制,其次还有电量、能耗等。所以我们将终端计算能力放到一个强算力中心上,将用户终端设备变成三部分:手势输入、屏幕输出,计算单元放到远端计算服务器上,它的算力要数倍甚至是几十倍于端上。

      image.png

      基于分布式的前提,输入、计算和输出的传输过程的耗时变短。考虑到未来5G网络、边缘计算的发展,在边缘节点和终端之间的传输速度,加上边缘计算节点的计算耗时,可能要比你在本机输入到本机芯片计算的耗时还要短。
      所以,我们设计了一套分布式的云端渲染和实时计算方案。一方面解决交互的方式,大计算量的实时的数据场景。另一方面,借鉴在游戏领域(如云游戏)的思路,以下是设计模型:
      image.png

      1、对于用户的手机终端、VR设备或眼镜类各种设备,因为硬件在不断发展,它的算力会越来越强。但是个别设备算力还比较弱,所以我们希望有实时调度能力。算力强的设备,在端上做;算力弱的设备,在云上做。同时基于用户的手机电量等各方面场景,在边端体系上有一个调度能力。用户端的一个播放行为,其实是从媒资的存储到转码、CDN分发,CDN节点,通过分发服务到手机终端,当用户点击视频,通过对应的时间节点拉取对应的云端视频数据。
      2、在云渲染链路上,我们希望用户是通过调度的操作,决定计算逻辑是在端上还是边缘节点上。如果在边缘节点,通过边缘节点去访问中心节点,拉取到数据。当用户再次操作时,通过边缘节点进行相应的交互处理,再下发到端侧。这样从边缘节点到播放终端,是点对点的实时传输的操作。
      细化云渲染的整体设计,我将它分为五个部分:边缘服务框架、网络协议、端侧交互引擎、边端调度系统、应用开发工具链。其中边缘服务框架、网络协议、端侧交互引擎如下图所示,分别承担着边缘节点的框架服务能力、网络通信的协议处理、以及终端的交互、渲染引擎。而边端调度系统如上所说,主要是根据用户终端、边缘节点算力等情况合理调度用户的渲染服务是应该在终端处理还是到边缘节点处理。而基于此,我们可以看到,很大程度上服务程序是需要在多平台基础上运行的,所以相应的开发工具链(开发调试IDE、服务部署发布系统等)也是很重要的部分。

      image.png

      在边缘服务上,我们希望搭建一套基础框架,不仅承载现有的渲染服务,未来也可以部署游戏引擎来实现云游戏的服务。由于单个边缘服务节点需要服务多个终端设备,推拉流服务的用户session 管理很重要,并且低延时的推流处理、高性能的渲染服务等都是我们需要突破的重点。同时,由于我们定义的很多场景是基于实时计算和强交互的模式,更像是游戏,上行的数据以操作指令、文本等为主,下行则主要是流媒体数据、算法数据等,而且考虑到时延等问题,优选基于UDP构建的传输协议,同时考虑到网络穿透率的问题,基于TCP的方案会作为基础的兜底策略。而在端侧,重点是低延时的直播播放器,网络协议的客户端实现以及用户上行的指令处理等。

      四、未来的思考和总结

      始于播放新交互,结合5G和边缘计算,面向云渲染。基于这个链路,未来我们希望的播放新交互是什么样子?
      image.png

      首先,在交互能力上,我们已经将算法和内容做结合,视频内容本质上是导演、演员基于剧情,向用户传递信息。用户观影过程中,是不是可以跟导演、演员或内容之间有联动交互。
      其次,如何将这两者之间的信息拉通?就是通过算法结合内容做识别,算法本身去识别内容,再将识别出的内容跟用户看到的内容,在信息上更贴合用户偏好,将更多主动权交给用户,给用户更沉浸式的观影体验。同时因为这种交互的模式,对算法对算力有更高要求,借由5G和边缘计算的发展,打造一个环形体系,实现播放新交互的体系化形态。
      以上就是我们对于未来在播放和交互领域的思考。

      ]]>
      周二直播预告:数据库最佳实践_问题诊断-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 开发者经常会遇到些数据库的问题,觉得无从下手,这严重影响了开发效率,也影响了开发者对数据库的热情。本课程通过实际的场景以及最佳实践出发,带给大家一些数据库问题的通用解决思路和方法,大家会发现数据库不再是一个黑盒,相反它看得见,摸得着,也能够轻松玩得转。

      一、如何解决数据库问题提高开发效率?
      二、如何降低数据库使用门槛以及运维的成本?
      三、如何在较短的时间内用云数据库的技术和理念来武装自己,提升自己?

      6月16日(周二)14:00 阿里云高级数据库专家——郑旦 ,讲解《数据库最佳实践_问题诊断》

      欢迎大家钉钉扫描二维码,加入直播群内免费看直播!

      image.png

      ]]>
      如何轻松学习 Kubernetes?-阿里云开发者社区 Fri, 20 Jun 2025 02:20:34 +0800 什么是 Kubernetes?

      我们来看一下什么是 Kubernetes。这部分内容我会从四个角度来跟大家分享一下我的看法。

      1.未来什么样

      image.png

      这是一张未来大部分公司后端 IT 基础设施的架构图。简单来说,以后所有公司的 IT 基础设施都会部署在云上。用户会基于 Kubernetes 把底层云资源分割成具体的集群单元,给不同的业务使用。而随着业务微服务化的深入,服务网格这样的服务治理逻辑会变得跟下边两层一样,成为基础设施的范畴。

      目前,阿里基本上所有的业务都跑在云上。而其中大约有一半的业务已经迁移到了自己定制 Kubernetes 集群上。另外据我了解,阿里计划今年完成 100% 的基于 Kubernetes 集群的业务部署。

      而服务网格这块,在阿里的一些部门,像蚂蚁金服,其实已经有线上业务在用了。大家可以通过蚂蚁一些同学的分享来了解他们的实践过程。

      虽然这张图里的观点可能有点绝对,但是目前这个趋势是非常明显的。所以未来几年, Kubernetes 肯定会变成像 Linux 一样的,作为集群的操作系统无处不在。

      2.Kubernetes 与操作系统

      image.png

      这是一张传统的操作系统和 Kubernetes 的比较图。大家都知道,作为一个传统的操作系统,像 Linux 或者 Windows,它们扮演的角色,就是底层硬件的 一个抽象层。它们向下管理计算机的硬件,像内存或 CPU,然后把底层硬件抽象成一些易用的接口,用这些接口,向上对应用层提供支持。

      而 Kubernetes 呢,我们也可以把它理解为一个操作系统。这个操作系统说白了也是一个抽象层,它向下管理的硬件,不是内存或者 CPU 这种硬件,而是多台计算机组成的集群,这些计算机本身就是普通的单机系统,有自己的操作系统和硬件。Kubernetes 把这些计算机当成一个资源池来 统一管理,向上对应用提供支撑。

      这里的应用比较特别,就是这些应用都是容器化的应用。如果对容器不太了解的同学,可以简单把这些应用,理解为一个应用安装文件。安装文件打包了所有的依赖库,比如 libc 这些。这些应用不会依赖底层操作系统的库文件来运行。

      3.Kubernetes 与 Google 运维解密

      image.png

      上图中,左边是一个 Kubernetes 集群,右边是一本非常有名的书,就是 Google 运维解密这本书。相信很多人都看过这本书,而且有很多公司目前也在实践这本书里的方法。包括故障管理,运维排班等。

      Kubernetes 和这本书的关系,我们可以把他们比作剑法和气功的关系。不知道这里有多少人看过笑傲江湖。笑傲江湖里的华山派分两个派别,气宗和剑宗。气宗注重气功修炼,而剑宗更强调剑法的精妙。实际上气宗和剑宗的分家,是因为华山派两个弟子偷学一本葵花宝典,两个人各记了一部分,最终因为观点分歧分成了两派。

      Kubernetes 实际上源自 Google 的集群自动化管理和调度系统 Borg,也就是这本书里讲的运维方法所针对的对象。Borg 系统和书里讲的各种运维方法可以看做是一件事情的两个方面。如果一个公司只去学习他们的运维方法,比如开了 SRE 的职位,而不懂这套方法所管理的系统的话,那其实就是学习葵花宝典,但是只学了一部分。

      Borg 因为是 Google 内部的系统,所以我们一般人是看不到的,而 Kubernetes 基本上继承了 Borg 在集群自动化管理方面非常核心的一些理念。所以如果大家看了这本书,觉得很厉害,或者在实践这本书里的方法,那大家一定要深入理解下 Kubernetes。

      4.技术演进史

      image.png

      早期的时候,我们做一个网站后端,可能只需要把所有的模块放在一个可执行文件里,就像上图一样,我们有 UI、数据和业务三个模块,这三个模块被编译成一个可执行文件,跑在一台服务器上。

      但是随着业务量的大幅增长,我们没有办法,通过升级服务器配置的方式来扩容。这时候我们就必须去做微服务化了。

      微服务化会把单体应用拆分成低耦合的小应用。这些应用各自负责一块业务,然后每个应用的实例独占一台服务器,它们之间通过网络互相调用。

      这里最关键的是,我们可以通过增加实例个数,来对小应用做横向扩容。这就解决了单台服务器无法扩容的问题。

      微服务之后会出现一个问题,就是一个实例占用一台服务器的问题。这种部署方式,资源的浪费其实是比较严重的。这时我们自然会想到,把这些实例混部到底层服务器上。

      但是混部会引入两个新问题,一个是依赖库兼容性问题。这些应用依赖的库文件版本可能完全不一样,安装到一个操作系统里,必然会出问题。另一个问题就是应用调度和集群资源管理的问题。

      比如一个新的应用被创建出来,我们需要考虑这个应用被调度到哪台服务器,调度上去之后资源够不够用这些问题。

      这里的依赖库兼容性问题,是靠容器化来解决的,也就是每个应用自带依赖库,只跟其他应用共享内核。而调度和资源管理就是 Kubernetes 所解决的问题。

      顺便提一句,我们可能会因为,集群里混部的应用太多,这些应用关系错综复杂,而没有办法去排查一些像请求响应慢这样的问题。所以类似服务网格这类服务治理的