阿里云块存储团队软件工程实践对外版

丰富的线上&线下活动,深入探索云世界

做任务,得社区积分和周边

最真实的开发者用云体验

让每位学生受益于普惠算力

让创作激发创新

资深技术专家手把手带教

遇见技术追梦人

技术交流,直击现场

海量开发者使用工具、手册,免费下载

极速、全面、稳定、安全的开源镜像

开发手册、白皮书、案例集等实战精华

为开发者定制的Chrome浏览器插件

作者:晴筱、石超、张小路

“我背上有个背篓,里面装了很多血泪换来的经验教训,我看着你们在台下嗷嗷待哺想要这个背篓里的东西,但事实上我给不了你们”,实践出真知。

Uglyiseasytoidentifybecausethemessesalwayshavesomethingincommon,butnotbeauty.

--C++之父BjarneStroustrup

代码质量与其整洁度成正比。

--《代码整洁之道》作者RobertC.Martin

别人眼中的软件系统犹如灯火辉煌的摩天大厦,维护者眼中的软件系统犹如私搭乱建的城中村,我们要在这座城中村里生存,一直维护这些代码,添加新功能等,要让大家生活得更好,我们写代码不仅追求正确性,还有健壮性和可维护性。

图1开发理想与现实

要点1:语义简单明确

这是块存储SDK的一段代码,判断限流目标值是否合法;写代码时考虑读者,优先采取易于读者理解的写法。

#defineTHROTL_UNSET-2#defineTHROTL_NO_LIMIT-1boolthrottle_is_quota_valid(int64_tvalue){//复杂的判断条件//请你在三秒内说出value如何取值是合法的?if(value<0&&value!=THROTL_UNSET&&value!=THROTL_NO_LIMIT){returnfalse;}returntrue;}boolthrottle_is_quota_valid(int64_tvalue){//这是修改后的代码,value取值合法有三种情况,一目了然returnvalue>=0||value==THROTL_UNSET||value==THROTL_NO_LIMIT;}

要点2:简洁≠代码短

voidRecycleBin::Load(BindCallbackR1*done){......FOREACH(iter,fileStats){RecycleFileitem;Statusstatus=ParseDeletedFileName(iter->path,&item.timestamp);if(!status.IsOk(){......}item.fileName=iter->path;item.size=iter->size;item.physicalSize=iter->refCount>10:iter->physicalSize;......//这是修改前的代码//earliestTimestamp[item.medium]=//item.timestamp!=0&&item.timestamp

要点3:提前返错

提前返错能减少主体逻辑的缩进数量,让主体代码逻辑显得更醒目。

BadCase如下:

StatusFoo(){Statusstatus=Check1();if(!status.IsOk()){returnstatus;}else{status=Check2();if(!status.IsOk()){returnstatus;}else{status=Check3();if(!status.IsOk()){returnstatus;}else{DoSomeRealWork();returnOK;//四层潜套if}}}}GoodCase如下:

StatusFoo(){Statusstatus=Check1();if(!status.IsOk()){returnstatus;}status=Check2();if(!status.IsOk()){returnstatus;}status=Check3();if(!status.IsOk()){returnstatus;}DoSomeRealWork();returnOK;}

要点4:利用析构函数做清理工作

利用C++析构函数做清理工作,在复杂冗长代码中不会漏掉。典型的清理工作有执行回调、关闭文件、释放内存等。

voidFoo(RpcController*ctrl,constFooRequest*request,FooResponse*response,Closure*done){Statusstatus=Check1(request);if(!status.IsOk()){response->set_errorcode(status.Code());//第一处done->Run();return;}status=Check2(request);if(!status.IsOk()){response->set_errorcode(status.Code());//第二处done->Run();return;}DoSomeRealWork(...);//第三处done->Run();}GoodCase如下:

voidFoo(RpcController*ctrl,constFooRequest*request,FooResponse*response,Closure*_done){//仅一处,不遗漏erpc::ScopedCallbackdone(_done);Statusstatus=Check1(request);if(!status.IsOk()){response->set_errorcode(status.Code());return;}status=Check2(request);if(!status.IsOk()){response->set_errorcode(status.Code());return;}DoSomeRealWork(...);}

要点5:用朴素直观的算法

这是块存储旁路系统的一段代码,它根据垃圾比对数据分片进行排序;在非关键路径上,优先使用朴素直观的算法,此时代码可维护性更重要。

voidCompactTask::checkFileUtilizationRewrite(){//此处采取朴素的排序算法,并未采取更高效的TopK算法std::sort(sealedFilesUsage.begin(),sealedFilesUsage.end(),GarbageCollectionCompare);int64_tsealedFileMaxSize=INT64_FLAG(lsm_CompactionSealedMaxSize);int32_tsealedFileMaxNum=INT32_FLAG(lsm_CompactionSealedMaxFileNum);int64_ttargetFileSize=0;int32_tsourceFileCnt=0;//前者简单清淅,并在几十个File中选择前几个文件的场景并不算太慢FOREACH(itr,sealedFilesUsage){LogicalFileIdfileId=itr->fileId;constFileUsage*usage=baseMap->GetFileUsage(fileId);constFile*file=fileSet->GetFile(fileId);targetFileSize+=usage->blocks*mBlockSize;sourceFileCnt++;if(targetFileSize>sealedFileMaxSize||sourceFileCnt>sealedFileMaxNum){break;}mRewriteSealedFiles[fileId]=true;}......}

要点6:用轮循代替条件变量

这是块存储IO路径的一段代码,从内存中卸载数据分片时等待在途inflight的IO请求返回;在非关键路径上使用简单的轮循代替精巧的条件变量同步,代码简洁且不容易出bug。

voidUserRequestControl::WaitForPendingIOs(){erpc::ExponentialBackoffdelayTimeBackOff;delayTimeBackOff.Reset(INT64_FLAG(lsm_UnloadWaitingBackoffBaseUs),INT64_FLAG(lsm_UnloadWaitingBackoffLimitUs),INT64_FLAG(lsm_UnloadWaitingBackoffScaleFactor));//轮循等待在途的请求返回//请思考如何用条件变量实现精确的同步while(!mWriteQueue.empty()||!mReadQueue.empty()){uint64_tdelayTime=delayTimeBackOff.Next();PGLOG_INFO(sLogger,(__FUNCTION__,"Waitingforinflightrequestsduringsegmentunload")("Segment",mSegment->GetName())("WriteRequests",mWriteQueue.size())("ReadRequests",mReadQueue.size())("DelayTimeInUs",delayTime));easy_coroutine_usleep(delayTime);//退避等待}}

要点7:使用timed_wait代替wait

在典型的生产者消费者实现中,使用timedwait代替wait,避免生产者未正确设置条件变量造成消费者卡死无法服务的窘境。

pthread_mutex_tmutex;pthread_cond_tnonEmptyCondition;std::listqueue;voidConsumerLoop(){pthread_mutex_lock(&mutex);while(true){while(queue.empty()){structtimespects;ts.tv_sec=1;ts.tv_nsec=0;//使用timewaitpthread_cond_timedwait(&nonEmptyCondition,&mutex,timespec);}Task*firstTask=queue.front();queue.pop_front();consume(firstTask);}pthread_mutex_unlock(&mutex);}

要点8:用协程代替异步回调

这是块存储BlockServer加载数据分片的代码;用异步回调方式难以实现这样的复杂控制逻辑,用协程却能轻松实现。

//load.cppStatusLoadTask::Execute(){Statusstatus;#defineRUN_STEP(func)\status=func();if(!status.IsOk()){...}//串行执行下列步骤RUN_STEP(doPrepareDirs);......//十几步RUN_STEP(doTask);#undefRUN_STEP......}//files.cppStatusFileMap::SealFilesForLiveDevice(){Statusstatus=OK;std::vector*>sealDones;STLDeleteElementsGuard*>>donesDeleter(&sealDones);//并行seal每个文件FOREACH(iter,mActiveFiles){File*file=iter->second;sealDones.push_back(newSyncClosureR1());Closure*work=stone::NewClosure(this,&FileMap::doSealFileForLiveDevice,file,static_cast*>(sealDones.back()));InvokeCoroutineInCurrentThread(work);}//收集结果FOREACH(done,sealDones){(*done)->Wait();if(!(*done)->GetResult0().IsOk()){status=(*done)->GetResult0();}}returnstatus;}

要点9:在关键对象增加magic字段

这是块存储核心主路径的一段代码;在关键数据结构中增加magic字段和断言检查,能及时发现内存错误(例:内存踩坏)。

通常在下列两类结构增加magic:

1)关键的数据结构,如数据分片结构;

2)异步操作的上下文结构,如用户IOBuffer请求;

//stream.hclassStream{public:Stream();~Stream();voidRead(ReadArgs*args);......private://增加magic字段//通常使用uint32或uint64uint64_tmObjectMagic;......};//stream.cpp//定义magic常量//常量值选择hexdump时能识别的字符串,以便在gdb查看coredump时快速识别//此处使用“STREAM”的ASCII串staticuint64_tSTREAM_OBJECT_MAGIC=0x4e4d474553564544LL;Stream::Stream():mObjectMagic(STREAM_OBJECT_MAGIC)//在构造函数中赋值{......}Stream::~Stream(){//在析构函数中检查并破坏magic字段,预防double-free错误easy_assert(mObjectMagic==STREAM_OBJECT_MAGIC);mObjectMagic=FREED_OBJECT_MAGIC;......}voidDeviceSegment::Read(ReadArgs*args){//在重要的函数中检查magic字段,预防use-after-free错误easy_assert(mObjectMagic==DEVICE_SEGMENT_OBJECT_MAGIC);......}

要点10:SanityCheck()合法性检查

这是块存储核心模块的一段代码StreamWriter负责管理正在写入的Stream,它为每个写请求选择合适的Stream写入,并处理文件满、写失败等异常情形;曾在线下测试发现由于未添加合法性检查,导致内存踩坏的meta错误数据持久化到磁盘中,在数据分片发生迁移时,从磁盘加载错误的meta数据持续夯死,不可恢复。在重要操作前后及定时器中检查数据结构中的重要的不变式假设,这样尽早发现代码bug在重要的操作前后或是在定时器中执行检查。

classStreamWriter{public:......private:structStreamGroup{WriteAttemptListfailureQueue;WriteAttemptListinflightQueue;WriteAttemptListpendingQueue;uint64_tcommitSeq;uint64_tlastSeq;};uint32_tmStreamGroupCount;StreamGroupmStreamGroups[STREAM_GROUP_COUNT];......};voidStreamWriter::sanityCheck(){#ifndefNDEBUG//expensivechecksfor(uint32_ti=0;icommitSeq;constWriteAttemptList*queues[]={&group->failureQueue,&group->inflightQueue,&group->pendingQueue};for(size_tk=0;kwrite;PANGU_ASSERT(prevSeq<=write->seq);//SanityCheckprevSeq=write->seq+write->lbaRange.rangeSize;}}ASSERT(prevSeq==group->lastSeq);//SanityCheck}......#endif//NDEBUG}

要点11:用告警代替进程崩溃

在多租户系统中,单租户出现严重问题不应影响其他租户的服务。

在块存储,我们仅允许检查对象magic和线程是否正确的断言。其它断言由告警代替。

我们当前使用的内核配置HZ=1000,jiffies变量每49天溢出,Linux将jiffies变量初始值设置为负数,使系统启动后5分钟发生第一次溢出;让这段容易出错的危险代码每天都被执行到,这些再也不用担心出现黑天鹅事件了。

linux/include/linux/jiffies.h/**Havethe32bitjiffiesvaluewrap5minutesafterboot*sojiffieswrapbugsshowupearlier.*/#defineINITIAL_JIFFIES((unsignedlong)(unsignedint)(-300*HZ))/**TheseinlinesdealwithtimerwrappingcorrectlyYouare*stronglyencouragedtousethem*1.Becausepeopleotherwiseforget*2.Becauseifthetimerwrapchangesinfutureyouwon'thaveto*alteryourdrivercode.**time_after(a,b)returnstrueifthetimeaisaftertimeb.*/#definetime_after(a,b)\(typecheck(unsignedlong,a)&&\typecheck(unsignedlong,b)&&\((long)((b)-(a))<0))

要点14:避免有歧义的函数名和参数表

图2测试原则、可测性

要点1:边界测试

TEST_F(...,SharedDisk_StopOneBs)(...){BenchMarkStart(mOption);//for循环反复注入mCluster->StopServer(0);mCluster->StartServer(0);//修复前无第12行无代码,无下限检查,全部失败时CasePASS//共享盘开盘后线程死锁必IOHang,有测试无断言遗漏Bug导致P1故障EXPECT_GT(mIoBench->GetLastPrintIops(),0);EXPECT_GT(mIoBench->GetMaxLatency(),0);//断言检查,边界上限EXPECT_GT(20*1000000,mIoBench->GetMaxLatency());//Dosomethingbelow}StatusPRConfig::Register)(...){assertIoThread();//修复前缺少=,导致SeverCrashif(unlikely(mRegistrants.size()>=MAX_REGISTRANT_NUM)){LOG_ERROR(...);returnSC_RESERVATION_CONFLICT;}//Dosomethingbelow}

要点2:状态/分支测试

以下是块存储两个历史生产Bug;状态流程图,影响数据正确性和服务可用性的关键路径、异常分支、状态组合需测试覆盖。

voidWalStreamWriterPool::tryCreateWalWriter(){AssertCoroutine();ASSERT_DEBUG(mIsCreating);Statusstatus=OK;while(...){WalStreamWriter*writer=mWalManager->CreateWalWriter();status=writer->Open();//修复前无第14行代码部分,未处理Commit,失败导致丢掉WAL文件,进而丢数据if(status.IsOk()){status=mWalManager->Commit();}//Dosomethingbelow}voidRPCController::StartCancel(){if(_session){if(_pendingRpc!=NULL){//修复前无第29行代码,线程Hang进而IOHang//未测试覆盖callStartCancelbeforehandshake_session->need_cancel=true;}else{easy_session_cancel(_session);}}else{easy_error_log(...);}}

要点3:重复/幂等性测试

StatusCompressOffsetTable::Seal(){//Dosomethingbeforestatus=mTableFile->Seal();if(!status.IsOk()){PGLOG_ERROR(...);returnstatus;}mIsSealed=true;//修复前无第14行代码,文件写入已完成,清空缓存,释放内存mEasyPool.reset();//Dosomethingbelow}

要点4:兼容性测试

兼容性包含:协议兼容性、API兼容性、版本升级兼容性、数据格式兼容性;对于所有依赖的兼容性假设需通过测试自动化覆盖,兼容性问题是很难测试覆盖并且问题高发的部分,兼容性问题应该在设计阶段、编码阶段提前预防,避免兼容性问题,而非寄希望于兼容性测试来兜底。

voidActiveManager::SubmitIO({//【版本兼容性】SDK和Server线程不对齐,旧版本SDK不支持切线程if(UNLIKELY(GetCurrentThread()!=serverThread))PGLOG_WARNING(..."Serverthreadmismatch");response->ErrorCode=SERVER_BUSY;done->Run();}voidChunkListAccessor::SetChunkInfoAndLocations(){uint8_tflags=mFileNodePtr->fileFlags;boolisLogFile=IsFlatLogFile(flags);ASSERT(//【协议兼容性】Master和SDK异常场景定长误判(isLogFile&&vecChunkInfoNode[0].version<=masterChunkInfo.version)||!isLogFile);//Dosomethingbelow}//【API兼容性】Server和Master的错误码不一致,数据分片反复加载/卸载//Master侧,device_load.cpp//if(status.Code()==LSM_SEGMENT_EXIST_OTHER_VERSION))//Server侧,device_load.cpp//returnLSM_NOT_OWN_SEGMENT;

要点5:防御性测试

要点6:避免写出不稳定Case

Case不稳定真是一个让人头大问题,总结了一些不稳定的测试常见原因,希望大家记住并知行合一。

TEST_F(FastPathSmokeTestFixture,Resize){//...DosomethingResizeVolume(uri,DEVICE_SIZE*2);Statusstatus=OK;do{//状态依赖,未检查resize是否成功,导致错误的认为是越界io处理status=Write(handle,wbuf.get(),0,4096);if(status.Code()==OK){break;}easy_coroutine_usleep(100*1000);}while(1);//...Dosomething}//volume_iov_split_test.cppTEST(VolumeIovSplitTest,Iovsplit_Random){//...Dosomethingsize_ttotalLength=0;//修改前无+1,0是非法随机值,造成Case低概率失败totalLength=rand()%(10*1024*1024)+1//...Dosomething}二、本地工具2.1Docker单机集群对于分布式系统,能够在开发机上自测端到端的跨模块/跨集群的功能测试,极大的提高测试效率和开发幸福感。在开发调试期间,Docker集群用完即抛,拥有属于自己的无污染的“一手”功能测试集群,代码主路径必现的进程Crash均可在开发阶段发现。Docker使用极少的系统资源,有效地将不同容器互相隔离,快速创建分布式应用程序,非常适合集群测试使用。

块存储在没有Docker单机集群之前,测试集群级功能测试至少需要12台物理机,我们通过将块存储、盘古和女娲的服务装进容器中,实现单机OneBox,在开发机上(物理机/虚拟机/Docker/Mac均可,无OS依赖),一键秒级部署和销毁一个集群,基于Docker单机集群实现DockerFuntionTest,沿用单元测试的gtest,上手门槛低,极大的提高了测试效率和开发幸福感,DockerFuntionTest是在代码门禁中运行,即代码提交入库之前自动触发测试,在代码入库之前,百分之百拦截必现的进程Crash问题。

图3块存储Docker单机集群

研发效能低下的团队的一个典型表现,质量强依赖全链路端到端(End-To-End,简称E2E),测试环境维护成本极高,常常因为环境污染导致无效测试,是否能够将全链路E2E测试实现白屏化,告别环境修复?

在开发期间调试,不可避免有大Size的Patch修改,代码门禁UnitTest/SmokeTest/FunitionTest仅覆盖功能测试,涉及到IO性能、运维、用户态文件系统、用户态网络协议的代码逻辑修改,无法在代码门禁覆盖。面对这个问题,块存储开发者可以在开发机编译出包,测试平台白屏自助验证E2E测试,操作共3个步骤:编译上传包→提交测试任务→查看测试结果。降低测试门槛可以有效的提高测试的主观能动性,进而提高测试运行频次,当测试不再是负担的时候,大家更愿意做测试,谁会拒绝投资少收益高的事呢?

图4测试平台自助E2E

图5EBSUT/ST/FT

图6块存储代码门禁

预防胜于治疗,研究表明高效的CodeReview可以发现70-90%的bug,Review作用如下:

图7CodeReview可以发现70-90%的bug

对于Submitter和Reviewer的共同建议,开放的心态,良好的互动,Submitter给到reviewer更多的输入后,有益于问题的挖掘。

图8一个Review互动的优秀案例

要点1:ForSubmitter-一次提交不要超过400行代码

图9缺陷密度vs代码行数

要点2:ForSubmitter-做自己的第一个reviewer

自己的狗粮自己先吃,自我Review有以下几个注意事项:

图10CodeReview一个Description的优秀案例

要点1:ForReviewer-控制review速度

图11缺陷密度vs检查速度

要点2:ForReviewer-Review的重心

要点3:ForReviewer-Tips

南门:主干开发(trunkbaseddevelopment)是持续集成(continuousintegration)的最高阶段

在开发模式上,块存储学习了微软、Google的大库主干开发方式,过去多分支开发初期并行迭代,开发周期宽松,但多分支开发很容易漏提交,块存储也曾因漏提交bugfix导致一起P1S1故障,多分支合并冲突多、迭代慢,需要长期占用多套测试环境,有限的测试资源回归频次相对少。块存储所有持续集成测试资源均集中在主干分支回归,“集中力量办大事”。

主干开发对开发者提出了很高的要求,不仅要具备有功能特性拆分的能力,而且需要确保每一次代码提交,都能够达到准上线的质量标准,这也倒闭了测试左移的编码习惯,在提交代码入库之前进行测试编码。

图12块存储主干开发

发布模式分两种,公有云是主干分支发布,专有云是LTS(LongTimeSupport)拉Release分支发布(专有云发布节奏需follow客户要求,公有云发布节奏自主可控)。主干发布,既不是目的,也不是手段。主干发布是结果,是测试能力不断提升后水到渠成的结果,块存储持续集成交付详见第八章。切勿为了主干发布而主干发布,严格控制发布变量和发布节奏,避免为了修复一个bug,引入了另一个更严重的bug,只有经过成熟的测试验证方可进行发布,欲速则不达,敬畏生产。

除了代码门禁的功能Case覆盖之外,FeatureOwner需要补充代码后置全链路测试E2E和BVTCase,大版本发布需要经过大规模故障演练的验证方可发布上线,UT/ST/FT每月回归三千多轮,对于信用分低的Case(即不稳定Case)运行轮数权重翻五倍,即每月运行上万轮,通过高频测试尽量多暴露各层面的不稳定因素,倒逼人肉环节的自动化。块存储按照不同的场景提供了多种测试脚手架进行测分级,越轻量的测试回归轮数越多,更多详细说明如下图所示。

图13块存储测试分级

对于测试环境的标准化,采用两个思路:云原生用完即抛、资源池化集中管理。

图14块存储集群健康度检查

图15git-poison投毒/解药/银针

图16“集群-模版-Bug-版本”四维度生产画像

质量防控的思路和疫情防控的思路很像,首先在在源头处,通过代码门禁及时发现,就像是疫情的入境检查;持续集成,每日主干回归,缩短反馈弧,就像是现在“每3天一核酸,全员检测”,一有问题,马上定位修复。

图17块存储从开发到上线

通过微服务化,将单次版本百人协同的成本降低到十人以下,彻底告别“发大车”,各自聚焦,各行其道,互不干扰。

图18块存储分模块发布

图19分模块发布后兼容性测试

开发工具:工欲善其事,必先利其器。工具没有好与坏,要看你如何使用,一个高效的工具事半功倍,推荐一些日常工具:

工作方法:良好的工作方法,可以让自己的成长速度形成复利,推荐阅读两本书《直击本质》和《系统之美》,或许你可以从书中找到不同人成长速率差异的答案,这里推荐三个工作方法:

对于校招同学,职场童年最重要的是养成良好的工作习惯,身份从学生到工程师,必然伴随着成长的阵痛感,从学生思维转向职场思维,从学习驱动转向任务交付驱动,运用学校里基础知识,长期锻炼的学习思维,快速达到独立交付状态。不论任务多小,要能独立负责,交付好结果,事事有着落,承担任务,从小到中到大,从简单到复杂,从尝试型到突破型。

THE END
1.升仕350D最全参数配置表?1开屏就是狗粮暴击。看到很多人在问具体参数和配置。从有限的官方宣传资料里总结了一些关键参数(图二)和特色配置(图三)。希望能帮助到喜欢这个车的小伙伴们。2350D关键参数。3350D特色配置。另外还看到商城已上架了除首发银白https://m.58moto.com/news/7393651
2.伯纳天纯狗粮中大型成犬金毛拉布拉多阿拉斯加萨摩边牧粮15kg参数详细参数 品牌 伯纳天纯(pure Natural) 类别 狗干粮 国产/进口 国产 产地 中国安徽芜湖市 适用犬种 柴犬,雪纳瑞,哈士奇,拉布拉多,金毛,松狮,阿拉斯加,萨摩耶,藏獒,德牧,边牧,通用 适用年龄 成年期(1-7岁) 口味 羊肉味 保质期 1.5年 热门产品推荐 ¥299.00 麦富迪狗粮 [牛肉双拼粮肉粒含肉量90%]泰迪...https://m.suning.com/itemcanshu/0070714861/000000010946985348.html
3.用于治疗高钾血症不共同施用锂的硅酸锆的制作方法在一实施方案中,本发明产品能在施用后约至少1、2、4、6、8、10小时,降低升高的水平。降低升高的钾水平需要的剂量可以是约5–15克,优选8–12克,更优选10克。具有约2.5–4.7meq/g的更高的KEC分子筛应在吸收钾方面是更有效的。因此,降低升高的钾水平所需剂量可以是约1.25–6克。剂量施用时间表可以是至少...https://www.xjishu.com/zhuanli/05/201480074868.html
4.泰山巴哈马雪茄参数雪茄123答:烟支长度是84mm,包装规格为硬包装。4、泰山(好客细支)香烟:依据山东中烟官方网站发布的该产品信息可知,其当前的零售价为220元/条,折合成单包则为22元。该香烟产品为烤烟型,烟支长度是97mm,包装规格为硬包装。 2021-03-22 回答者: 格拉腾天然狗粮 1个回答 14 ...http://www.xuejia123.com/c/391/539/2023012420974.html
5.声明:本产品可以替代迈信EP100a声明:本产品可以替代迈信EP100-3A,功能参数完全一样,并添加新一代的软件算法,进一步提升硬件抗干扰能力,诚意客户(48小时确认收货)本店承诺免费保修期从原来的一年延长至一年半,终身技术支持! Statement: This product may substitute steps believes EP100-3A, the function parameter is completely same, and ...http://riyu.zaixian-fanyi.com/fan_yi_7932137
1.鲜朗烘焙狗粮怎么样?体验后讲实情内幕?购物资讯百家评测鲜朗烘焙狗粮是一种高品质的狗粮,其主要成分是鲜肉和鱼肉。这种狗粮可以帮助您的宠物摄取充足的蛋白质和脂肪,从而保证其健康成长。此外,鲜朗烘焙狗粮还含有丰富的维生素和微量元素,可以帮助您的宠物提高免疫力,并促进皮肤和毛发的健康生长。 鲜朗烘焙狗粮产品参数: ...http://www.baidudw.com/news/1008113
2.关于市场调研报告(15篇)(1) 产品销售收入指标(见下表): 大型企业累计实现产品销售收入813.13亿元,增幅比上年同期上升了9.29个百分点,增幅比行业平均水平低4.58个百分点,产品销售收入占比比上年同期下降了0.99个百分点; 中型企业累计实现产品销售收入326.93亿元,增幅比上年同期上升了11.89个百分点,增幅比行业平均水平低8.39个百分点,产品销售收入...https://www.ruiwen.com/diaoyanbaogao/7106270.html
3.使用大语言模型(LLMs)构建产品一年后的经验总结(第一部分)[译...最后,要考虑文档提供的细节程度。想象我们正在构建一个 RAG 系统,从自然语言生成 SQL 查询。我们可以简单地提供表结构和列名作为上下文,但如果包括列描述和一些代表性值,额外的细节将帮助 LLM 更好地理解表的语义,从而生成更正确的 SQL。 别忘了关键词搜索:把它作为基线并在混合搜索中使用 ...https://blog.csdn.net/cq20110310/article/details/139364121
4.TicWatchC2智能手表值不值得买TicWatchC2全面评测硬件综合实际戴上手的感觉还是挺舒服的,手表不会很重,接触手腕的表体也打磨的很圆润,不会硌手,总体来说,TicWatch C2的佩戴舒适度我还是很满意的,没什么太大问题。 另外值得一说的是,这次TicWatch C2还特地推出了首款女性专属的玫瑰金版,这个版本的特点在于,重量更轻,表带宽度从原来的20mm变到18mm,看上去纤细很多,更符合...https://www.jb51.net/hardware/zonghe/667717.html
5.泰诺宠物粮狗粮机器设备型号齐全,宠物食品生产线提供宠物...泰诺犬粮、宠物食品机器设备主机采用双螺杆挤压膨化机可以通过调整原料、温度、水分等工艺参数,使产品具有形状新颖、口感独特、营养丰富、组织细腻之特点。 1.整条宠物食品生产线物料接触部位采用不锈钢材料,完全符合食品卫生条件。 2.经过长时间的改进,目前狗粮设备可以加工鲜肉超过40%的狗粮,***颠覆以前单螺杆小机器鲜...https://tainuo201219.cn.china.cn/supply/4497376818.html
6.宠物自动喂食器到底值不值得买?3款自动喂食器完全测评小米的设计延续了米家系列产品一贯的风格,电源线可以收纳起来,非常elegant。食盆也做了倾斜设计,让猫咪吃粮更方便。 (3)小佩 小巧轻便。 总体参数对比表 注:小顽 = 小米 总之,这三款喂食器各有千秋,都能满足日常的需求,在安全性上也都考虑的比较周到。 https://m.douban.com/note/768634920/
7.个性化狗粮全球及中国市场规模研究和预测2024第七章:本章重点分析个性化狗粮上下游市场情况,上游市场分析个性化狗粮主要原料供应现状及主要供应商,下游市场主要分析个性化狗粮的主要应用领域,每个领域的消费量 ,未来增长潜力。 第八章:本章分析中国市场个性化狗粮的进出口贸易现状及趋势,重点分析中国个性化狗粮产量、进口量、出口量 及表观消费量关系,以及未来国内市场...https://www.shangyexinzhi.com/article/22916430.html
8....APP降睡眠监测黑色报价参数图片怎么样皇家狗粮 金毛成犬狗粮 犬粮 大型犬 GR25 通用粮 15月以上 3.5KG产品口碑 暂无评分 暂时没有人点评该产品 竞品对比 更多对比 小毛球宠物追踪器壳套-粉红天使(粉红色) 已停产 +对比 小毛球宠物追踪器-黑色恶魔(黑色) 已停产 +对比 小毛球宠物追踪器壳套-黑色恶魔(黑色) 已停产 +对比 小毛球宠物追...https://g.pconline.com.cn/product/othercellphoneaccessories/aidis/714743.html
9.自制边境牧羊犬狗粮宠物食品生产线:打造形状多样口味丰富...边境牧羊犬狗粮机 幼犬狗粮营养生产设备 ?0?2 ?0?2 1.宠物食品 ?0?2 宠物食品生产线赋予宠物食品独特的形状、风味和颜色,以最佳营养配比、易消化的特点适应日益增长的宠物食品市场。合理的结构设计、特殊的材料、快速的自清洁性能确保了机器的稳定性,更方便维护。操作简单易学,精确的参数控制确保产...https://xkyczp.com/detail/id/174971.html
10.双螺杆挤压膨化狗粮设备挤压膨化温度参数控制参考双螺杆挤压膨化狗粮设备挤压膨化温度参数的控制参考对于膨化狗粮的品质至关重要。作为济南市美腾机械设备有限公司,我们将继续致力于科技创新和质量zhuoyue,不断提升我们的产品和服务,为客户提供更加完美的膨化狗粮生产线设备。欢迎广大客户来电咨询和洽谈合作!http://103740624.b2b.11467.com/news/7674411.asp
11.YF2100/YF3000产品简介 土壤养分分析仪技术参数|价格|报价|河南云飞科技土壤肥料养分速测仪主要用于土壤、肥料,植株中水分、盐分、ph值、全氮、铵态氮、碱解氮、有效磷、有效钾、钙、镁、硼等微量元素含量测试。 详细介绍 土壤养分分析仪技术参数|价格|报价|河南云飞科技土壤的污染问题不仅仅表现这几方面,还有很多问题,我们要...https://www.hbzhan.com/st658715/product_24968975.html