首页 文章 精选 留言 我的

精选列表

搜索[文字准确率],共9510篇文章
优秀的个人博客,低调大师

disuz 7.2文字常量定义文件messages.lang.php

当需要对disuz做一些修改时,可能会涉及到这个文件。 D:\hadoop\backup\20120619221410\templates\default\messages.lang.php <?php // Message Pack for Discuz! Version 1.0.0 // Created by Crossday $language = array ( 'undefined_action' => '未定义操作,请返回。', 'group_nopermission' => '您所在的用户组($grouptitle)无法进行此操作。', 'viewperm_none_nopermission' => '对不起,您无权访问该版块,详细请 点击这里查看 有权访问的用户组为:$permgroups', 'viewperm_upgrade_nopermission' => '对不起,您需要升级您所在的用户组后才能访问该版块,详细请 点击这里查看 有权访问的用户组为:$permgroups', 'viewperm_login_nopermission' => '对不起,您还未登录,无权访问该版块。', 'postperm_none_nopermission' => '对不起,您无权在该版块发帖,详细请 点击这里查看 有权发帖的用户组为:$permgroups', 'postperm_upgrade_nopermission' => '对不起,您需要升级您所在的用户组后才能发帖,详细请 点击这里查看 有权发帖的用户组为:$permgroups', 'postperm_login_nopermission' => '对不起,您还未登录,无权在该版块发帖。', 'replyperm_none_nopermission' => '对不起,您无权在该版块回帖,详细请见 点击这里查看 有权回帖的用户组为:$permgroups', 'replyperm_upgrade_nopermission' => '对不起,您需要升级您所在的用户组后才能回帖,详细请 点击这里查看 有权回帖的用户组为:$permgroups', 'replyperm_login_nopermission' => '对不起,您还未登录,无权在该版块回帖。', 'getattachperm_none_nopermission' => '对不起,您无权下载本附件,详细请 点击这里查看 有权下载附件的用户组为:$permgroups', 'getattachperm_upgrade_nopermission' => '对不起,您需要升级您所在的用户组后才能下载本附件,详细请 点击这里查看 有权下载附件的用户组为:$permgroups', 'getattachperm_login_nopermission' => '对不起,您还未登录,无权下载本附件。', 'postattachperm_none_nopermission' => '对不起,您无权上传附件,详细请 点击这里查看 有权上传附件的用户组为:$permgroups', 'postattachperm_upgrade_nopermission' => '对不起,您需要升级您所在的用户组后才能上传附件,详细请 点击这里查看 有权上传附件的用户组为:$permgroups', 'postattachperm_login_nopermission' => '对不起,您还未登录,无权上传附件。', 'group_expiry_disabled' => '您目前没有即将到期的用户组,请返回。', 'period_nopermission' => '对不起,管理员设置了每日 $banperiods 不能进行此操作或访问此页面,请其他时间再试。', 'submit_seccode_invalid' => '您输入的验证码不正确,无法提交,请返回修改。', 'submit_secqaa_invalid' => '验证问答回答错误,无法提交,请返回修改。', 'submit_invalid' => '您的请求来路不正确,无法提交。', 'not_loggedin' => '对不起,您还未登录,无法进行此操作。', 'action_closed' => '论坛尚未开启此功能,或者此功能已经被管理员禁止,请返回。', 'board_closed' => '对不起,本论坛暂时关闭,详情请 联系管理员。', 'max_pages' => '管理员设置了本内容可以被翻阅到的最大页数为 $pages 页,如需查看相关内容,请返回并指定查询条件后再试。', 'login_invalid' => '登录失败,您还可以尝试 $loginperm 次 $seccodescript ', 'login_clearcookie' => '登录痕迹已经清除', 'login_question_empty' => '请填写安全提问以及正确的答案', 'login_question_invalid' => '安全提问选择错误,请重新填写', 'login_strike' => '密码错误次数过多,请 15 分钟后重新登录', 'login_succeed' => '欢迎您回来,{$discuz_userss}。现在将转入登录前页面。 $ucsynlogin ', 'login_succeed_inactive_member' => '欢迎您回来,{$discuz_userss}。您的帐号处于非激活状态,现在将转入控制面板。 $ucsynlogin ', 'logout_succeed' => '您已退出论坛,现在将以游客身份转入退出前页面。 [ 清除痕迹 ] $ucsynlogout ', 'login_activation' => '您的帐号未在本论坛激活,现在将转入帐号激活页面。', 'user_banned' => '对不起,您的 IP 地址不在被允许的范围内,或您的账号被禁用,无法访问本论坛。', 'word_banned' => '对不起,您填写的内容(如签名、帖子、短消息等)包含不良内容而无法提交,请返回修改。', 'forum_nonexistence' => '指定的版块不存在,请返回。', 'forum_passwd' => '本版块需要密码', 'forum_passwd_correct' => '密码验证成功,现在将转入主题列表。', 'forum_passwd_incorrect' => '您输入的密码不正确,不能访问这个版块。', 'forum_nopermission' => '本版块只有特定用户组可以访问。', 'forum_permforum_nopermission' => '您需要满足以下条件才可访问这个版块 访问条件: $formulamessage您的信息: $usermsg ', 'forum_permforum_nopermission_custommsg' => ' $formulamessage ', 'forum_permforum_nomedal' => '您需要拥有以下勋章才可访问这个版块 $formulamessage ', 'forum_permforum_disallow' => '本版块只有特定用户可以访问。', 'forum_disablepost' => '本版块禁止发帖。', 'forum_access_disallow' => '对不起,您在本版无权进行此操作,请返回', 'forum_access_view_disallow' => '对不起,您在本版无权进行此操作,请返回', 'forum_access_post_disallow' => '对不起,版主已经禁止您在本版发布新主题,请返回', 'forum_access_reply_disallow' => '对不起,版主已经禁止您回复本版的帖子,请返回', 'forum_access_postattach_disallow' => '对不起,版主已经禁止您上传附件,请返回', 'forum_access_getattach_disallow' => '对不起,版主已经禁止您下载附件,请返回', 'thread_nonexistence' => '指定的主题不存在或已被删除或正在被审核,请返回。', 'thread_nopermission' => '对不起,本帖要求阅读权限高于 $thread[readperm] 才可浏览,请返回。', 'thread_poll_closed' => '本主题已关闭,无法继续投票,请返回。', 'thread_poll_voted' => '您已参与过这个投票,请返回。', 'thread_poll_invalid' => '您没有选择投票选项,请返回修改。', 'thread_poll_succeed' => '您的投票成功提交,现在将转入主题页。', 'thread_rate_moderator_invalid' => '对不起,作为版主您只能在自己的管辖范围内评分,请返回。', 'ajax_thread_rate_moderator_invalid' => '对不起,作为版主您只能在自己的管辖范围内评分。', 'thread_rate_range_invalid' => '您未输入分值或超过每次评分范围限制,请返回修改。', 'ajax_thread_rate_range_invalid' => '分值超过限制。', 'thread_rate_timelimit' => '对不起,您不能对发表于 $karmaratelimit 小时前的帖子进行评分,请返回。', 'ajax_thread_rate_timelimit' => '对不起,您不能对发表于 $karmaratelimit 小时前的帖子进行评分。', 'thread_rate_member_invalid' => '对不起,您不能给自己发表的帖子评分,请返回。', 'ajax_thread_rate_member_invalid' => '对不起,您不能给自己发表的帖子评分。', 'thread_rate_ctrl' => '24 小时评分数超过限制,请返回修改。', 'thread_rate_duplicate' => '对不起,您不能对同一个帖子重复评分,请返回。', 'ajax_thread_rate_duplicate' => '对不起,您不能对同一个帖子重复评分。', 'thread_rate_succeed' => '感谢您的参与,现在将转入评分前页面。', 'ajax_thread_rate_succeed' => '感谢您的参与。', 'thread_rate_removesucceed' => '评分已成功撤销,现在将转入评分前页面。', 'thread_rate_anonymous' => '对不起,您不能对匿名帖评分,请返回。', 'ajax_thread_rate_anonymous' => '对不起,您不能对匿名帖评分。', 'thread_rate_banned' => '对不起,您不能对屏蔽帖评分,请返回。', 'ajax_thread_rate_banned' => '对不起,您不能对屏蔽帖评分。', 'thread_rate_log_nonexistence' => '对不起,指定帖子在近一年内没有评分记录,请返回。', 'thread_pay_succeed' => '您已成功购买本主题,现在将转入主题页面。', 'thread_report_disabled' => '对不起,管理员关闭了报告帖子功能,请返回。', 'thread_report_existence' => '对不起,您已经报告过这篇帖子,请返回。', 'ajax_thread_report_disabled' => '对不起,管理员关闭了报告帖子功能。', 'thread_report_flood_ctrl' => '对不起,您两次报告间隔不得少于 $floodctrl 秒,请返回。', 'ajax_thread_report_flood_ctrl' => '对不起,您两次报告间隔不得少于 $floodctrl 秒。', 'thread_maxpostsperhour_invalid' => '对不起,本站规定会员每小时最多只能发表 $maxpostsperhour 个帖子。', 'thread_report_invalid' => '您没有选择对谁报告本帖,请返回修改。', 'thread_report_succeed' => '您的意见已经报告给版主和管理员,现在将转入主题页面。', 'ajax_thread_report_succeed' => '您的意见已经报告给版主和管理员。', 'thread_warning_nonexistence' => '指定帖子没有警告记录,请返回。', 'thread_warning_existence' => '对不起,本帖已经被警告过,请返回。', 'digest_nonexistence' => '对不起,指定范围内没有精华帖,请返回。', 'customtopics_updated' => '用户专题更新完毕。', 'view_log_invalid' => '浏览量记录文件($logfile)无法写入,请设置其属性 777 或删除后重新建立。', 'attachment_referer_invalid' => '对不起,请不要从外部链接下载本论坛的附件。', 'attachment_nopermission' => '对不起,本附件要求阅读权限高于 $attach[readperm] 才可下载,请返回。', 'attachment_forum_nopermission' => '对不起,只有特定用户可以下载本论坛的附件,请返回。', 'attachment_nonexistence' => '附件文件不存在或无法读入,请与管理员联系。', 'attachment_payto' => '附件所在主题需要付费,请您付费后下载。', 'attachement_payto_attach' => '附件需要付费,请您付费后下载。', 'attachment_yetpay' => '您已经购买过此附件,无需重复购买,现在将开始附件下载。', 'attachment_buy' => '您已成功购买本附件,现在将开始下载“{$attach[filename]}”。', 'attachment_expired' => '对不起,原附件链接已失效。 [ 重新下载请点击这里 ] [ 查看原帖请点击这里 ]', 'attachment_credit' => '已扣除{$policymsg},现在将开始下载“{$attach[filename]}”。', 'post_hide_nopermission' => '对不起,您没有权限使用 [hide] 代码,请返回修改。', 'post_url_nopermission' => '对不起,您没有权限发表 URL 连接,请返回修改。', 'post_newbie_span' => '对不起,您只在注册时间起 $newbiespan 小时后才有发帖权限,请返回。', 'post_allowspecialonly' => '对不起,本论坛只允许发布特殊类型主题,请返回。', 'post_forum_newthread_nopermission' => '对不起,本版块只有特定用户组可以发新话题,请返回。', 'post_forum_newreply_nopermission' => '对不起,本版块只有特定用户组可以回复,请返回。', 'post_thread_closed' => '对不起,本主题已关闭,不再接受新回复,请返回。', 'post_thread_closed_by_dateline' => '对不起,管理员设置了本版块发表于 $forum[autoclose] 天以前的主题自动关闭,不再接受新回复,请返回。', 'post_thread_closed_by_lastpost' => '对不起,管理员设置了本版块最后回复于 $forum[autoclose] 天以前的主题自动关闭,不再接受新回复,请返回。', 'post_subject_toolong' => '对不起,您的标题超过 80 个字符,请返回修改标题长度。', 'post_message_tooshort' => '对不起,您的帖子小于 $minpostsize 个字符的限制,请返回修改。', 'post_message_toolong' => '对不起,您的帖子超过 $maxpostsize 个字符的限制,请返回修改。', 'post_sm_isnull' => '您没有输入标题或内容,请返回填写。', 'post_type_isnull' => '您没有选择主题的类别,请返回修改。', 'post_sort_isnull' => '您没有选择主题的分类,请返回修改。', 'post_flood_ctrl' => '对不起,您两次发表间隔少于 $floodctrl 秒,请不要灌水!', 'post_net_price_iszero' => '您的主题售价扣除积分交易税后为 0,请返回修改。', 'post_poll_option_toomany' => '对不起,您的投票选项超过 $maxpolloptions 个,请返回修改。', 'post_edit_reward_nopermission' => '对不起,悬赏已经结束,无法退还积分,请返回。', 'post_edit_reward_already_reply' => '对不起,您不能删除已经有回复的悬赏,请返回。', 'post_edit_thread_already_reply' => '对不起,您不能删除已经有回复的主题,请返回。', 'post_edit_nopermission' => '对不起,您无权编辑他人发表的帖子,请返回。', 'post_edit_timelimit' => '对不起,您的帖子从发表到现在已超过 $edittimelimit 分钟,不能再进行编辑,请返回。', 'post_edit_moderate' => '对不起,管理员设置了需要对发帖进行审核,您无权编辑已通过审核的帖子,请返回。', 'post_edit_delete_rushreply_nopermission' => '对不起,抢楼贴是不能删除的,请返回', 'post_edit_delete_succeed' => '主题删除成功,现在将转入主题列表。', 'post_attachment_toobig' => '对不起,您的附件大小超过论坛限制,请返回修改。', 'post_attachment_type_toobig' => '对不起,您的附件超过 \".$extension\" 类型 $typemaxsize 的限制,请返回修改。', 'post_attachment_ext_notallowed' => '对不起,不支持上传此类扩展名的附件,请返回修改。', 'post_attachment_size_invalid' => '对不起,您上传的附件尺寸为 0,系统不允许上传此类附件,请返回修改。', 'post_attachment_quota_exceed' => '对不起,您最近 24 小时上传的附件已超过 $maxsizeperday 的限制,无法继续上传,请返回或稍后再试。', 'post_attachment_save_error' => '附件文件无法保存到服务器,可能是目录属性设置问题,请与管理员联系。', 'post_attachment_remote_save_error' => '附件文件无法保存到远程服务器,请与管理员联系。', 'post_attachment_image_checkerror' => '图片附件不合法(类型 $type, 宽 $width, 高 $height),请返回修改。', 'post_edit_succeed' => '您的帖子编辑成功,现在将转入主题页。 [ 需要转入主题列表请点击这里 ]', 'post_reply_succeed' => '非常感谢,您的回复已经发布,现在将转入主题页。 [ 需要转入主题列表请点击这里 ]', 'post_check' => '指定的帖子不存在或已被删除或正在被审核。 [ 尝试转入主题请点击这里 ]', 'post_reply_mod_succeed' => '非常感谢,管理员设置了需要对新回复进行审核,您的帖子通过审核后将被显示出来,现在将转入主题列表。', 'post_newthread_succeed' => '非常感谢,您的主题已经发布,现在将转入主题页。 [ 需要转入主题列表请点击这里 ]', 'edit_reply_mod_succeed' => '非常感谢,管理员设置了需要对编辑后的回复进行审核,您的帖子通过审核后将被显示出来,现在将转入主题列表。', 'edit_newthread_mod_succeed' => '管理员设置了对编辑后的主题进行审核,您的帖子通过审核后才会显示。', 'post_newthread_blog_succeed' => '非常感谢,您的文章已经发布,现在将转入文集。', 'post_newthread_blog_invalid' => '对不起,您没有权限或管理员设置了禁止在本版发表文章,请返回。', 'post_newthread_mod_succeed' => '管理员设置了对新主题进行审核,您的帖子通过审核后才会显示。', 'post_upload_error' => '文件上传错误,请重新选择。', 'register_disable' => '对不起,目前论坛禁止新用户注册,请返回。', 'register_rules_agree' => '您必须同意服务条款后才能注册', 'register_disable_activation' => '对不起,目前论坛禁止激活,请返回。', 'register_ctrl' => 'IP 地址在 $regctrl 小时内无法注册', 'register_flood_ctrl' => 'IP 地址在 24 小时内只能注册 $regfloodctrl 次', 'register_check_found' => '用户名已经被他人使用', 'register_invite_notfound' => '没有填写邀请码不能注册', 'register_invite_error' => '邀请码不正确', 'register_invite_iperror' => '注册用户 IP 和邀请码拥有者 IP 相同', 'register_invite_expiration' => '邀请码已过期', 'register_check_notfound' => '恭喜您,您输入的用户名 \"$username\" 没有人使用,可以继续注册。', 'register_manual_verify' => '非常感谢您的注册,管理员设置了人工验证新注册用户,请等待审核通过,现在将转入控制面板首页。', 'register_succeed' => '感谢您注册 $bbname,现在将以会员身份登录论坛。', 'register_succeed_newbietask' => '感谢您的注册,我们诚邀您参与新手任务 $task[name],现在将转入任务详情页面。', 'register_activation' => '用户名已经存在', 'register_activation_message' => '对不起,您输入的用户名 \"$username\" 已经存在,请登录论坛激活此帐号。', 'register_activation_invalid' => '激活失败,请重新登录验证需要激活的用户。', 'profile_username_toolong' => '对不起,您的用户名超过 15 个字符,请返回输入一个较短的用户名。', 'profile_username_tooshort' => '对不起,您输入的用户名小于 3 个字符, 请返回输入一个较长的用户名。', 'profile_passwd_notmatch' => '两次输入的密码不一致', 'profile_passwd_wrong' => '原密码不正确,您不能修改密码或 Email 或安全提问,请返回。', 'profile_passwd_empty_wrong' => '您没有填写新密码,请返回。', 'profile_admin_security_invalid' => '作为管理者之一,您需要填写安全提问和答案以保障论坛的安全,请返回。', 'profile_username_duplicate' => '该用户名已经被注册', 'profile_uid_duplicate' => '用户 ID $uid 已经被占用', 'profile_email_duplicate' => '该 Email 地址已经被注册', 'profile_username_illegal' => '用户名包含敏感字符', 'profile_username_protect' => '用户名包含被系统屏蔽的字符', 'profile_nickname_cstatus_illegal' => '昵称或自定义头衔包含敏感字符或被系统屏蔽,请返回重新填写。', 'profile_passwd_illegal' => '密码空或包含非法字符', 'profile_email_illegal' => 'Email 地址无效', 'profile_email_domain_illegal' => 'Email 包含不可使用的邮箱域名', 'profile_alipay_illegal' => '支付宝账号无效,请返回重新填写。', 'profile_alipay_msn' => 'MSN 账号无效,请返回重新填写。', 'profile_required_info_invalid' => '您没有填写必填项目,请返回修改。', 'profile_message_keyword' => '注册原因须包含关键词“hadoop”', 'profile_sig_toolong' => '您的签名长度超过 $maxsigsize 字符的限制,请返回修改。', 'profile_avatar_invalid' => '您设置的头像类型无效,请返回修改。', 'profile_avatar_toobig' => '您设置的头像超过 $maxavatarsize 字节大小,请返回修改。', 'profile_avatar_size_invalid' => '读取远程头像尺寸失败,如确认头像地址无误,请返回输入头像的宽、高代替原有的“*”。', 'profile_avatardir_nonexistence' => '头像目录 ./images/avatars 不存在,请联系管理员。', 'profile_avatar_succeed' => '您头像设置已成功更新,现在将转入个人资料页。', 'profile_email_verify' => '确认 Email 已经发送,请用邮件中提供的方法激活您的帐号。如果您没有收到我们发送的系统邮件,请点击控制面板首页中的“重新验证 Email 有效性”,或尝试更换另外一个地址。注意:在完成激活之前,根据管理员设置,您将只能以待验证会员的身份访问论坛,您可能不能进行发帖等操作。激活成功后,上述限制将自动取消。', 'profile_succeed' => '个人资料已成功保存。', 'credits_disabled' => '对不起,管理员没有启用论坛积分。', 'credits_transaction_disabled' => '对不起,交易积分没有被启用,无法使用此功能,请返回或与管理员联系。', 'credits_transaction_amount_invalid' => '您要转账或兑换的积分数量输入有误,请返回修改。', 'credits_password_invalid' => '您没有输入密码或密码错误,请返回。', 'credits_policy_lowerlimit' => '对不起,您的{$extcredits[$id][title]}不足(小于 {$extcredits[$id][lowerlimit]} {$extcredits[$id][unit]})。', 'credits_balance_insufficient' => '对不起,您的账户余额不足(少于 $minbalance),请返回。', 'credits_buy_thread' => '对不起,您已经购买过此主题,请勿重复付费。', 'credits_net_amount_iszero' => '本交易扣除积分交易税后的净值为 0,请返回修改。', 'credits_transfer_send_nonexistence' => '您没有输入接收人或接收人不存在,请返回修改。', 'credits_transfer_self' => '对不起,您不能对自己转账', 'credits_exchange_invalid' => '您选择原积分和目标积分相同,不能进行兑换,请返回修改。', 'credits_transaction_succeed' => '积分操作成功。', 'credits_addfunds_amount_invalid' => '您单次购买的积分数量超出最大值 $ec_maxcredits 或最小值 $ec_mincredits 的限制。', 'credits_addfunds_ctrl' => '您两次提交订单间隔过小。', 'credits_addfunds_toomuch' => '您超出本月允许购买 $ec_maxcreditspermonth 的限额。', 'credits_addfunds_order_invalid' => '当前订单号无效,请在上一页重新提交,给您带来不便敬请谅解。', 'credits_addfunds_succeed' => '订单成功提交', 'usergroups_toomany' => '您设置的扩展用户组超过系统存储容量,请返回修改。', 'usergroups_span_invalid' => '对不起,您至少需要支付 $group[minspan] 天的积分价格,请返回。', 'usergroups_join_succeed' => '成功加入用户组,现在将转入公众用户组设置。', 'usergroups_exit_succeed' => '成功退出用户组,现在将转入公众用户组设置。', 'usergroups_update_succeed' => '公众用户组成功更新,现在将转入公众用户组设置。', 'usergroups_nonexistence' => '对不起,没有这个用户组,请返回。', 'username_nonexistence' => '指定用户不存在,请返回修改。', 'buddy_add_invalid' => '用户已存在于您的好友列表中。', 'buddy_add_toomany' => '您已经设定了超过 20 位好友。', 'buddy_update_succeed' => '成功添加为好友。', 'buddy_add_ignore' => '用户拒绝加您为好友。', 'redirect_nextnewset_nonexistence' => '没有比当前更新的主题,请返回。', 'redirect_nextoldset_nonexistence' => '没有比当前更早的主题,请返回。', 'favorite_thread_exists' => '您曾经收藏过这个主题 [不再收藏此主题] [查看我收藏的主题]', 'favorite_forum_exists' => '您曾经收藏过这个版块 [查看我收藏的版块]', 'favorite_add_thread_succeed' => '此主题已成功添加到收藏夹中 [查看我收藏的主题]', 'favorite_add_forum_succeed' => '此版块已成功添加到收藏夹中 [查看我收藏的版块]', 'favorite_remove_thread_succeed' => '此主题已成功从您的收藏夹中移除。', 'favorite_remove_forum_succeed' => '此版块已成功从您的收藏夹中移除。', 'favorite_update_succeed' => '收藏夹已成功更新,现在将转入更新后的收藏夹。', 'search_ctrl' => '对不起,您在 $searchctrl 秒内只能进行一次搜索,请返回。', 'search_toomany' => '对不起,管理员设置了每分钟系统最多响应搜索请求 $maxspm 次,请返回并稍后再试。', 'search_invalid' => '您没有指定要搜索的关键字或用户名,请返回重新填写。', 'search_threadtype_invalid' => '您没有输入要搜索的分类信息项目,请返回重新填写。', 'search_forum_invalid' => '您没有指定搜索论坛的范围,请返回重新填写。', 'search_id_invalid' => '您指定的搜索不存在或已过期。', 'search_redirect' => '搜索成功完成,现在将转入结果页面。', 'member_nonexistence' => '指定的用户不存在或已被删除,请返回。', 'member_list_disable' => '对不起,管理员禁止了会员列表功能。', 'email_friend_invalid' => '相关项目没有填写完整,请返回修改。', 'ajax_email_friend_invalid' => '相关项目没有填写完整。', 'email_friend_succeed' => '您的推荐已经通过 Email 发给朋友,现在将转入原帖。', 'ajax_email_friend_succeed' => '您的推荐已经通过 Email 发给朋友。', 'email_invite_succeed' => '您的邀请码已经通过 Email 发给朋友。', 'announcement_nonexistence' => '目前没有公告供查看,请返回。', 'mark_read_succeed' => '所有版块已被标记已读。', 'register_verify_invalid' => '管理员设置了人工验证新注册用户,请返回等待审核通过。', 'email_verify_invalid' => '您在 24 小时内只能提交一次验证 Email 请求,请返回。', 'email_verify_succeed' => '确认 Email 已经发送,请用邮件中提供的方法激活您的帐号。', 'submit_verify_succeed' => '审核请求提交成功,现在将返回控制面板首页。', 'activate_succeed' => '您好,$member[username],您的帐号已被成功激活,现在将转入论坛首页。', 'activate_illegal' => '您所用的 ID 不存在或您不是等待验证会员。', 'poll_overdue' => '投票已经过期,请返回。', 'poll_choose_most' => '此投票最多只能选择 {$pollarray[maxchoices]} 项,请返回修改。', 'poll_maxchoices_expiration_invalid' => '对不起,最多可选项数与有效记票天数只接受数字,请返回修改。', 'poll_end' => '投票已经结束,不能再投票。', 'trade_closed' => '本交易已关闭', 'trade_invalid' => '无效的交易请求', 'trade_credit_invalid' => '管理员已关闭积分交易功能,请返回。', 'trade_credits_no_enough' => '您的 {$extcredits[$creditstrans][title]} 不足,无法用 {$extcredits[$creditstrans][title]} 换取商品的优惠,请返回修改。', 'trade_by_myself' => '您无法购买自己的商品,请返回修改。', 'trade_input_no' => '请输入正确的商品数,请返回修改。', 'trade_lack' => '商品剩余数量不足,请返回修改。', 'trade_credit_lack' => '您的积分不足无法购买,请返回。', 'trade_alipay_please' => '对不起,支付宝账户不能为空,请返回修改。', 'trade_please_name' => '对不起,商品名称不能为空,请返回修改。', 'trade_price_between' => '对不起,商品人民币价格必须在 {$mintradeprice} 元至 {$maxtradeprice} 元之间,请返回修改。', 'trade_price_more_than' => '对不起,商品人民币价格必须大于 {$mintradeprice} 元,请返回修改。', 'trade_credit_between' => '对不起,商品积分价格必须在 {$mintradeprice} 至 {$maxtradeprice} 之间,请返回修改。', 'trade_credit_more_than' => '对不起,商品积分价格必须大于 {$mintradeprice} ,请返回修改。', 'trade_pricecredit_need' => '对不起,商品价格不能为空,请返回修改。', 'trade_pricecredit_error' => '对不起,商品基价输入错误,请返回修改。', 'trade_please_locus' => '对不起,商品所在地不能为空,请返回修改。', 'tread_please_number' => '对不起,商品数量不能为空,请返回修改。', 'trade_orderstatus_updated' => '交易单状态已更新。', 'trade_displayorder_updated' => '柜台商品摆放顺序已更新。', 'trade_order_created' => '交易单已创建,现在将转入交易单页面。', 'trade_password_error' => '您输入的密码不正确,不能修改订单状态,请返回修改。', 'trade_thread_succeed' => '非常感谢,您的商品主题已经发布,现在将转入添加商品页。', 'trade_add_succeed' => '非常感谢,您的商品已经发布,现在将转入商品主题页。 [ 继续添加商品请点击这里 ] [ 需要转入主题列表请点击这里 ]', 'trade_newreply_nopermission' => '对不起,只有已开放的商品主题可以回复,请返回。', 'trade_directtopay' => '订单提交成功,现在将跳转到支付页面。', 'reward_cant_operate' => '对不起,您不能设置最佳答案, 请返回。', 'reward_cant_self' => '对不起,您不能把自己的回复设置成最佳答案, 请返回。', 'reward_repeat_selection' => '不能重复选定最佳答案, 请返回。', 'reward_completion' => '最佳答案设置成功,请返回。', 'reward_credits_invalid' => '对不起,悬赏积分设置不合理,请返回修改。', 'reward_credits_please' => '对不起,悬赏积分不能为空,请返回修改。', 'reward_credits_fall' => '对不起,悬赏积分不能降低, 请返回修改。', 'reward_credits_overflow' => '对不起,悬赏积分不能高于 32767,请返回修改。', 'reward_credits_between' => '对不起,悬赏积分必须在 $minrewardprice 与 $maxrewardprice 之间, 请返回修改。', 'reward_credits_lower' => '对不起,悬赏积分必须大于 $minrewardprice, 请返回修改。', 'reward_credits_shortage' => '对不起,积分余额不足,请返回修改。', 'reward_end' => '对不起,请确认是否是悬赏帖或者悬赏已经结束。', 'reward_remove_succeed' => '悬赏已取消。', 'activity_stop' => '对不起,活动已停止申请,请返回。', 'activity_repeat_apply' => '对不起,活动不能重复申请。', 'activity_completion' => '活动申请成功,请等待发起人审核。', 'activity_choice_applicant' => '请选择活动的申请者。', 'activity_auditing_completion' => '活动人员审核完毕。', 'activity_delete_completion' => '活动列表清理完毕。', 'activity_fromtime_please' => '对不起,活动开始时间不能为空,请返回修改。', 'activity_fromtime_error' => '对不起,活动开始时间格式不正确,请返回修改。', 'activity_sort_please' => '对不起,活动所属类别不能为空,请返回修改。', 'activity_address_please' => '对不起,活动地点不能为空,请返回修改。', 'activity_totime_error' => '对不起,活动征集截止日期格式不正确,请返回修改。', 'activity_smaller_current' => '对不起,活动开始时间不能小于当前时间', 'post_poll_inputmore' => '对不起,您至少应当填写 2 个投票选项,请返回修改。', 'getpasswd_account_notmatch' => '用户名,Email 地址或安全提问不匹配,请返回修改。', 'getpasswd_account_invalid' => '管理员和超级版主不能使用取回密码功能,请返回。', 'getpasswd_illegal' => '您所用的 ID 不存在或已经过期,无法取回密码。', 'getpasswd_send_succeed' => '取回密码的方法发送到您的信箱中。', 'getpasswd_succeed' => '您的密码已重新设置,请使用新密码登录。', 'blog_nonexistence' => '指定的用户不存在,或其文集中没有文章。', 'blog_topic_nonexistence' => '指定的文章不存在或已被删除,请返回。', 'blog_category_isnull' => '当前作者在指定栏目内没有文集,请返回。', 'blog_add_illegal' => '对不起,您不是本主题的作者,无法进行此操作。', 'blog_add_succeed' => '操作成功。', 'threadmod_nonexistence' => '主题管理记录不存在,或管理记录已经过期并删除,请返回。', 'pm_newbie_span' => '对不起,您只在注册时间起 $newbiespan 小时后才有发短消息权限,请返回。', 'pm_send_disable' => '对不起,您没有发短消息的权限。', 'pm_nonexistence' => '对不起,消息不存在或已被删除。', 'pm_send_nonexistence' => '收件人不存在或存在重复,请返回修改。', 'pm_send_self_ignore' => '您不能给自己发消息,请返回修改。', 'pm_send_invalid' => '短消息发送失败,收件人拒收您发的消息,请返回。', 'pm_send_limit1day_error' => '对不起,超出了您24小时最大发送短消息数,请返回。', 'pm_send_floodctrl_error' => '对不起,您两次发送短消息间隔太短,请返回。', 'pm_send_batnotfriend_error' => '对不起,您不能给非好友批量发送短消息,请返回。', 'pm_send_pmsendregdays_error' => '对不起,您目前还不能发送短消息,请返回。', 'pm_send_succeed' => '短消息发送成功。', 'pm_send_empty' => '您没有填写短消息内容,请返回。', 'pm_delete_succeed' => '指定消息成功删除。', 'pm_mark_read_succeed' => '指定消息已标记为已读状态。', 'pm_mark_unread_succeed' => '指定消息已标记为未读状态。', 'pm_ignore_succeed' => '忽略列表已成功更新,现在将转入消息列表。', 'pm_addblack_succeed' => '指定用户的消息已被屏蔽', 'pm_addblackall_succeed' => '您已屏蔽所有人消息', 'pm_delblack_succeed' => '屏蔽设置已移除', 'plugin_nopermission' => '对不起,您没有权限使用此插件模块。', 'plugin_module_nonexistence' => '指定的插件模块文件($modfile)不存在或存在语法错误,请检查是否已将插件完整上传。', 'admin_nopermission' => '您没有权限使用管理功能。', 'admin_nonexistence' => '您没有选择操作项目。', 'admin_expiration_invalid' => '有效期无效。', 'admin_reason_invalid' => '请输入原因。', 'admin_moderate_invalid' => '您没有选择主题。', 'admin_getip_nopermission' => '对不起,您没有权限查看比您高级的管理员 IP,请返回。', 'admin_delpost_invalid' => '您没有选择要删除的帖子。', 'admin_banpost_invalid' => '您没有选择要屏蔽的帖子。', 'admin_move_illegal' => '目标版块与原版块相同。', 'admin_split_invalid' => '主题没有回复,无法分割。', 'admin_split_subject_invalid' => '您没有输入标题。', 'admin_split_new_invalid' => '您没有选择要分割入新主题的帖子,请返回修改。', 'admin_merge_nonexistence' => '主题 ID (tid) 不存在。', 'admin_merge_invalid' => '请选择其他主题进行合并。', 'admin_succeed' => '管理操作成功,现在将返回论坛。', 'admin_succeed_next' => '管理操作成功,现在将转入后续操作。', 'admin_move_invalid' => '目标版块不存在。', 'admin_move_have_mod' => '目标版块需要内容审核。', 'admin_move_nopermission' => '目标版块无发帖权。', 'admin_type_invalid' => '目标分类无效。', 'admin_repair_succeed' => '主题修复完成。', 'admin_warn_invalid' => '您没有选择要警告的作者的帖子。', 'admin_warn_nopermission' => '您无权警告该作者。', 'special_noaction' => '无法操作特殊主题。', 'special_refundment_invalid' => '特殊主题不能执行强制退款!', 'auditstatuson_succeed' => '审核成功,点击查看该帖。', 'audit_edit_succeed' => ' 编辑成功, 点击关闭本页。', 'todays_birthdays_banned' => '对不起,管理员禁止了显示生日会员功能。', 'switch_status_now' => '正在切换您的状态。', 'magics_close' => '道具中心关闭,请返回。', 'magics_market_close' => '道具市场关闭,请返回。', 'magics_market_operation_error' => '道具市场关闭,请返回。', 'magics_perm' => '您所在的用户组不允许使用道具系统,请返回。', 'magics_nonexistence' => '您选择的道具不存在,请到 道具商店购买。', 'magics_buy_nonexistence' => '您选择的道具不存在,请返回', 'magics_filename_nonexistence' => '道具文件所需文件 {$magicfile} 不存在。', 'magics_info_nonexistence' => '相关资料填写不全,请返回。', 'magics_nopermission' => '你所在的用户组不允许进行该操作,请返回。', 'magics_credits_no_enough' => '{$extcredits[$creditstran***tra[3]][title]} 不足,请修改购买道具数量。', 'magics_num_no_enough' => '库存不足,请修改购买道具数量。', 'magics_amount_no_enough' => '没有足够的道具可以操作,请修改。', 'magics_num_invalid' => '操作数量不合法,请返回修改。', 'magics_succeed' => '道具操作成功。', 'magics_post_anonymous' => '匿名帖不允许使用此道具,请返回。', 'magics_price_iszero' => '您的道具售价扣除积分交易税后为 0,请修改。', 'magics_price_invalid' => '道具设置价格不正确,请返回修改。', 'magics_price_high' => '道具设置价格过高,请返回修改。', 'magics_username_nonexistence' => '您没有输入要赠送的用户名,请返回。', 'magics_target_nopermission' => '对不起,不允许对目标对象使用该道具,请返回。', 'magics_target_nonexistence' => '对不起,您所指定的目标不存在,请返回。', 'magics_weight_range_invalid' => '超过最大负载量{$maxmagicsweight},当前负重量{$totalweight},请返回。', 'magics_give_myself' => '对不起,不能赠送给自己,请返回修改。', 'magics_give_succeed' => '赠送成功。', 'magics_mod_forbidden' => '对不起,此帖已被版主操作,禁止使用道具,请返回。', 'magics_operation_succeed' => '道具使用成功。', 'magics_operation_nopermission' => '对不起,此道具只能操作自己发的主题和帖子,请返回。', 'magics_use_nopermission' => '无权使用该道具,请返回。', 'magics_forum_nopermission' => '该板块不允许使用本道具,请返回。', 'magics_MOK_message' => '恭喜您,获得 {$getmoney} {$extcredits[$creditstran***tra[3]][title]}', 'magics_SEK_message' => '此人的IP为: {$post[useip]}', 'magics_RTK_on_message' => '你查询的会员不在线。', 'magics_RTK_off_message' => '你查询的会员在线,所在位置 {$actioncode[$msession[action]]}', 'magics_SOFA_message' => '已经被人抢了沙发,下次要快点啊!', 'task_close' => '论坛任务关闭,请返回。', 'task_nonexistence' => '该任务不存在或已被删除!请返回。', 'task_up_to_limit' => '对不起,该任务仅限 $task[tasklimits] 人领取奖励,按照先到先得的原则,您无法领取奖励,请下次再来吧!', 'task_completed' => '恭喜您,任务已成功完成,您将收到奖励通知短消息,请注意查收!', 'task_failed' => '对不起,您没能在指定时间内完成任务!', 'task_doing' => '您已完成该任务的 {$result[csc]}%,加油啊!', 'task_doing_rt' => '您已完成该任务的 {$result[csc]}%,还有{$result[t]}时间,加油啊!', 'task_waiting' => '您还没有开始执行任务,赶快哦!', 'task_waiting_rt' => '您还没有开始执行任务,还有{$result[t]}时间,赶快哦!', 'task_giveup' => '您已放弃此任务。', 'task_offline' => '该任务未上线或已下线!', 'task_full' => '该任务申请人数已满!', 'task_relatedtask' => '申请此任务需先完成一个任务,现在将转入这个任务页面。', 'task_grouplimit' => '您所在的用户组不允许申请此任务!', 'task_duplicate' => '您已经申请过此任务,请不要重复申请!', 'task_nextperiod' => '本期您已经申请过此任务,请下期再来申请吧!', 'task_applied' => '任务申请成功!', 'task_reward_credit' => '恭喜您完成任务,您已得到的积分奖励如下:{$extcredits[$task[prize]][title]} {$task[bonus]} {$extcredits[$task[prize]][unit]},详情请查阅短消息。', 'task_reward_magic' => '恭喜您完成任务,您已得到的道具奖励如下:{$magicname} {$task[bonus]} 枚,详情请查阅短消息。', 'task_reward_medal' => '恭喜您完成任务,您已得到的勋章奖励如下:{$medalname} 有效期 {$task[bonus]} 天,详情请查阅短消息。', 'task_reward_invite' => '恭喜您完成任务,您已得到的邀请码奖励如下:共 {$task[prize]} 个 有效期 {$task[bonus]} 天,详情请查阅短消息。', 'task_reward_group' => '恭喜您完成任务,您已得到的特殊用户组奖励如下:用户组 {$grouptitle} 有效期 {$task[bonus]} 天,详情请查阅短消息。', 'faq_content_empty' => '指定的帮助项目不存在,请返回。', 'faq_search_help' => '搜索帮助', 'faq_keywords_empty' => '您没有指定要搜索的关键字,请返回重新填写。', 'frame_off' => '对不起,没有开启左右分栏功能,请返回。', 'extcredits_disallowexchangeout' => '{$extcredits[$fromcredits][title]} 不允许兑出', 'extcredits_disallowexchangein' => '{$extcredits[$tocredits][title]} 不允许兑入', 'extcredits_dataerror' => '兑换失败,请与管理员联系。', 'tag_closed' => '对不起,管理员锁定了此 TAG。', 'eccredit_order_notfound' => '指定的订单不存在或已被删除。', 'eccredit_rate_repeat' => '不能重复评价。', 'eccredit_succeed' => '您的评价已经成功。', 'eccredit_nofound'=> '指定的评价不存在或已被删除。', 'eccredit_reexplanation_repeat' => '请勿重复解释。', 'eccredit_reexplanation_closed' => '对不起,允许解释的有效期已过!', 'invite_send' => '{$inviteuser}邀请您注册{$bbname}', 'invite_invalid' => '您填写的邀请码无效。', 'invite_close' => '邀请注册功能已关闭,请返回。', 'invite_nopermission' => '您所在的用户组不允许使用邀请注册。', 'invite_num_invalid' => ' 购买数量非法', 'invite_buy_succeed' => ' 购买成功', 'invite_mark_succeed' => '标记操作成功。', 'invite_num_buy_range_invalid' => ' 超过您所在用户组允许拥有的邀请码数量的最大值,请修改购买数量。', 'invite_num_range_invalid' => ' 您所拥有的邀请码数量已经到达最大值,请返回。', 'invite_credits_no_enough' => ' 您的 {$extcredits[$creditstran***tra[4]][title]} 不足。', 'debate_end' => '辩论已经结束,请返回。', 'debate_position_nofound' => '对不起,发表辩论主题需要输入正方和反方观点,请返回修改。', 'debate_endtime_invalid' => '对不起,您设置辩论的结束时间小于当前时间,请返回修改。', 'debate_nofound' => '对不起,该辩论主题不存在。', 'debate_poll_nopermission' => '对不起,请您先登录,然后再进行投票。', 'debate_poll_voted' => '您已经投过票。', 'debate_poll_end' => '投票已经结束,不能再投票。', 'debate_poll_succeed' => '投票成功。', 'debate_poll_myself' => '对不起,您不能给自己投票。', 'debate_umpire_invalid' => '对不起,你指定的裁判 ( {$umpire}) 不存在或者被删除,请返回修改。', 'debate_umpire_edit_invalid' => '您发表的评论已经超出了一个小时,您不能再进行编辑。', 'debate_umpire_nopermission' => '您不是裁判,不能对此辩论进行评判,请返回。', 'debate_umpire_nofound_bestdebater' => '您没有填写最佳辩手,请返回。', 'debate_umpire_nofound_winner' => '您没有填写获胜方,请返回。', 'debate_umpire_nofound_point' => '您没填写您的观点,请返回。', 'debate_umpire_bestdebater_invalid' => '您指定的最佳辩手不存在或者不合法,请返回修改。', 'debate_umpire_comment_succeed' => '评判成功。', 'debate_umpire_comment_invalid' => '裁判已经进行了评判,您不能再进行编辑,请返回。', 'debate_stand_invalid' => '您没有选择立场,请返回。', 'debate_stand_incorrect' => '您选择的立场与上一次不一致,请返回确认。', 'threadtype_required_invalid' => '资料填写不全,请返回检查{$checkoption[$var][title]}选项。', 'threadtype_format_invalid' => '资料格式不正确,请返回检查{$checkoption[$var][title]}选项。', 'threadtype_toolong_invalid' => '资料长度过长,请返回检查{$checkoption[$var][title]}选项。', 'threadtype_num_invalid' => '资料数值不正确,请返回检查{$checkoption[$var][title]}选项。', 'threadtype_unchangeable_invalid' => '资料不得修改,请返回检查{$checkoption[$var][title]}选项。', 'threadtype_expiration_invalid' => '此主题必须指定有效期,请返回修改。', 'reprint_invalid' => '转帖数据不存在,请返回。', 'rate_view' => '查看评分记录', 'seccode_player' => '', 'ajax_seccode_player' => ' {$flashcode} 播放验证码 ', 'medal_permforum_nopermission' => '您没有达到领取该勋章的条件 所需条件: $formulamessage您的信息: $usermsg ', 'medal_apply_invalid' => '对不起此勋章不可申请,请返回。', 'medal_apply_existence' => '您已经申请过或领取过勋章,请不要重复申请或领取。', 'medal_apply_succeed' => '申请勋章成功,正在等待管理员审核。', 'medal_get_succeed' => '恭喜你获得{$medal[name]}', 'modcp_moduser_invalid' => '审核失败, 没有找到相应的用户, 请返回', 'modcp_moduser_succeed' => '审核处理完毕', 'modcp_ann_empty' => '您必须输入公告标题和内容,请返回修改。', 'modcp_ann_urlerror' => '网址链接无效,请返回修改。', 'modcp_ann_nofound' => '该公告已经删除或您无权编辑它,请返回', 'modcp_logout_succeed' => '成功退出管理面板,返回论坛', 'modcp_mod_succeed' => '审核处理完毕', 'modcp_noaction' => '您没有选择执行任何操作', 'members_edit_succeed' => '用户信息成功更新,请继续操作', 'modcp_member_ban_succeed' => '用户资料成功更新', 'admin_cpanel_noaccess' => '您无权访问管理面板,请重新登录论坛,然后重试', 'admin_cpanel_locked' => '对不起,由于您多次输入错误密码,所以管理面板暂时锁定,您现在无法进入管理面板。 30 分钟以后,锁定会自动解除', 'admin_cpanel_noaccess_ip' => '对不起,管理员设定了只有特定 IP 地址范围才能访问系统设置,您的地址不在被允许的范围内', 'msn_binding_succeed' => '您已成功绑定/更换 MSN 账号!', 'payonline_succeed' => '您已成功支付。', 'favoritethreads_update_succeed' => '主题关注列表成功更新,现在将转入更新后的关注列表。', 'favoriteforums_update_succeed' => '版块关注列表成功更新,现在将转入更新后的关注列表。', 'favoritethreads_add_succeed' => '指定主题已成功加入到关注列表中 [查看我关注的主题]', 'favoritethreads_exists' => '您正在关注这个主题 [不再关注此主题] [查看我关注的主题]', 'favoritethreads_remove_succeed' => '已取消对此主题的关注。', 'favoriteforums_add_succeed' => '该版块已成功加入到关注列表中 [查看我关注的版块]', 'favoriteforums_exists' => '您正在关注这个版块 [查看我关注的版块]', 'favoriteforums_remove_succeed' => '已取消对此版块的关注。', 'recommend_duplicate' => '您已经评价过本主题了', 'recommend_outoftimes' => '您今天的评价机会已经用完,请明天再来', 'recommend_self_disallow' => '您不能评价自己的帖子', 'recommend_daycount_succed' => '本主题评价指数 $allowrecommend,您今天还能评价 $daycount 次', 'recommend_succed' => '本主题评价指数 $allowrecommend', 'recommend_expiration_invalid' => '时间期限格式无效,正确格式为“yyyy-mm-dd hh:mm”,请返回。', ); ?> 管理员在2009年8月13日编辑了该文章文章。 --> --> 阅读(1605) | 评论(1) | 转发(0) | 0 上一篇:使用valgrind检查cache命中率,提高程序性能 下一篇:设计mooon调度器遇到的难题

优秀的个人博客,低调大师

bat 文件里打印中文字符出现乱码的原因分析

这是典型的编码不一致:你的 .bat 文件大概率保存为 UTF-8,但 Windows cmd.exe 当前使用的是简体中文代码页 936,于是 UTF-8 字节被当成 GBK 解码,当前系统时间: 就变成了 褰撳墠绯荤粺鏃堕棿锛?。Microsoft 文档说明,chcp 用于切换控制台当前代码页,而代码页 936 对应简体中文编码;Microsoft 也建议优先使用 UTF-8 等 Unicode 编码。(Microsoft Learn)

优秀的个人博客,低调大师

轻松读懂 Dubbo,手写2W+文字 dubbo 原理解析(下篇)

01 服务发现1.1 服务发现流程 整体duubo的服务消费原理 Dubbo 框架做服务消费也分为两大部分 , 第一步通过持有远程服务实例生成Invoker,这个Invoker 在客户端是核心的远程代理对象 。 第二步会把Invoker 通过动态代理转换成实现用户接口的动态代理引用 。 服务消费方引用服务的蓝色初始化链,时序图 1.2 源码分析应用 引用入口:ReferenceBean 的getObject 方法,该方法定义在Spring 的FactoryBean 接口中,ReferenceBean 实现了这个方法。 publicObjectgetObject()throwsException{ returnget(); } publicsynchronizedTget(){ //检测ref是否为空,为空则通过init方法创建 if(ref==null){ //init方法主要用于处理配置,以及调用createProxy生成代理类 init(); } returnref; } Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。 privatevoidinit(){ //创建代理类 ref=createProxy(map); } 此方法代码很长,主要完成的配置加载,检查,以及创建引用的代理对象。这里要从createProxy 开始看起。从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并Invoker 实例。具体细节如下。 privateTcreateProxy(Map<String,String>map){ URLtmpUrl=newURL("temp","localhost",0,map); ........... isDvmRefer=InjvmProtocol.getlnjvmProtocol().islnjvmRefer(tmpUrl) //本地引用略 if(isJvmRefer){ }else{ //点对点调用略 if(url!=null&&url.length()>0){ }else{ //加载注册中心url List<URL>us=loadRegistries(false); if(us!=null&&!us.isEmpty()){ for(URLu:us){ URLmonitorUrl=loadMonitor(u); if(monitorUrl!=null){ map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString())); } //添加refer参数到url中,并将url添加到urls中 urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); } } } //单个注册中心或服务提供者(服务直连,下同) if(urls.size()==1){ //调用RegistryProtocol的refer构建Invoker实例 invoker=refprotocol.refer(interfaceClass,urls.get(0)); //多个注册中心或多个服务提供者,或者两者混合 }else{ List<Invoker<?>>invokers=newArrayList<Invoker<?>>(); URLregistryURL=null; //获取所有的Invoker for(URLurl:urls){ //通过refprotocol调用refer构建Invoker,refprotocol会在运行时 //根据url协议头加载指定的Protocol实例,并调用实例的refer方法 invokers.add(refprotocol.refer(interfaceClass,url)); if(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())){ registryURL=url; } } if(registryURL!=null){ //如果注册中心链接不为空,则将使用AvailableCluster URLu=registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); //创建StaticDirectory实例,并由Cluster对多个Invoker进行合并 invoker=cluster.join(newStaticDirectory(u,invokers)); }else{ invoker=cluster.join(newStaticDirectory(invokers)); } } } //省略无关代码... //生成代理类 return(T)proxyFactory.getProxy(invoker); } 上面代码很多,不过逻辑比较清晰。1、如果是本地调用,直接jvm 协议从内存中获取实例2、如果只有一个注册中心,直接通过Protocol 自适应拓展类构建Invoker 实例接口3、如果有多个注册中心,此时先根据url 构建Invoker。然后再通过Cluster 合并多个Invoker,最后调用ProxyFactory 生成代理类 (1)创建客户端 在服务消费方,Invoker 用于执行远程调用。Invoker 是由Protocol 实现类构建而来。Protocol 实现类有很多,这里分析DubboProtocol public<T>Invoker<T>refer(Class<T>serviceType,URLurl)throwsRpcException{ optimizeSerialization(url); //创建DubboInvoker DubboInvoker<T>invoker=newDubboInvoker<T>(serviceType,url, getClients(url),invokers); invokers.add(invoker); returninvoker; } 上面方法看起来比较简单,创建一个DubboInvoker。通过构造方法传入远程调用的client对象。默认情况下,Dubbo 使用NettyClient 进行通信。接下来,我们简单看一下getClients 方法的逻辑。 privateExchangeClient[]getClients(URLurl){ //是否共享连接 booleanservice_share_connect=false; //获取连接数,默认为0,表示未配置 intconnections=url.getParameter(Constants.CONNECTIONS_KEY,0); //如果未配置connections,则共享连接 if(connections==0){ service_share_connect=true; connections=1; } ExchangeClient[]clients=newExchangeClient[connections]; for(inti=0;i<clients.length;i++){ if(service_share_connect){ //获取共享客户端 clients[i]=getSharedClient(url); }else{ //初始化新的客户端 clients[i]=initClient(url); } } returnclients; } 这里根据connections 数量决定是获取共享客户端还是创建新的客户端实例,getSharedClient 方法中也会调用initClient 方法,因此下面我们一起看一下这个方法。 privateExchangeClientinitClient(URLurl){ //获取客户端类型,默认为netty Stringstr=url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY,Constants.DEFAULT_REMOTING_CLIENT)); //省略无关代码 ExchangeClientclient; try{ //获取lazy配置,并根据配置值决定创建的客户端类型 if(url.getParameter(Constants.LAZY_CONNECT_KEY,false)){ //创建懒加载ExchangeClient实例 client=newLazyConnectExchangeClient(url,requestHandler); }else{ //创建普通ExchangeClient实例 client=Exchangers.connect(url,requestHandler); } }catch(RemotingExceptione){ thrownewRpcException("Failtocreateremotingclientforservice..."); } returnclient; } initClient 方法首先获取用户配置的客户端类型,默认为netty。下面我们分析一下Exchangers 的connect 方法。 publicstaticExchangeClientconnect(URLurl,ExchangeHandlerhandler)throws RemotingException{ //获取Exchanger实例,默认为HeaderExchangeClient returngetExchanger(url).connect(url,handler); } 如上,getExchanger 会通过SPI 加载HeaderExchangeClient 实例,这个方法比较简单,大家自己看一下吧。接下来分析HeaderExchangeClient 的实现。 publicExchangeClientconnect(URLurl,ExchangeHandlerhandler)throws RemotingException{ //这里包含了多个调用,分别如下: //1.创建HeaderExchangeHandler对象 //2.创建DecodeHandler对象 //3.通过Transporters构建Client实例 //4.创建HeaderExchangeClient对象 returnnewHeaderExchangeClient(Transporters.connect(url,new DecodeHandler(newHeaderExchangeHandler(handler))),true); } 这里的调用比较多,我们这里重点看一下Transporters 的connect 方法。如下: publicstaticClientconnect(URLurl,ChannelHandler...handlers)throws RemotingException{ if(url==null){ thrownewIllegalArgumentException("url==null"); } ChannelHandlerhandler; if(handlers==null||handlers.length==0){ handler=newChannelHandlerAdapter(); }elseif(handlers.length==1){ handler=handlers[0]; }else{ //如果handler数量大于1,则创建一个ChannelHandler分发器 handler=newChannelHandlerDispatcher(handlers); } //获取Transporter自适应拓展类,并调用connect方法生成Client实例 returngetTransporter().connect(url,handler); } 如上,getTransporter 方法返回的是自适应拓展类,该类会在运行时根据客户端类型加载指定的Transporter 实现类。若用户未配置客户端类型,则默认加载NettyTransporter,并调用该类的connect 方法。如下: publicClientconnect(URLurl,ChannelHandlerlistener)throwsRemotingException { //创建NettyClient对象 returnnewNettyClient(url,listener); } (2)注册 这里就已经创建好了NettyClient对象。关于DubboProtocol 的refer 方法就分析完了。接下来,继续分析RegistryProtocol 的refer 方法逻辑。 public<T>Invoker<T>refer(Class<T>type,URLurl)throwsRpcException{ //取registry参数值,并将其设置为协议头 url=url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); //获取注册中心实例 Registryregistry=registryFactory.getRegistry(url); if(RegistryService.class.equals(type)){ returnproxyFactory.getInvoker((T)registry,type,url); } //将url查询字符串转为Map Map<String,String>qs= StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); //获取group配置 Stringgroup=qs.get(Constants.GROUP_KEY); if(group!=null&&group.length()>0){ if((Constants.COMMA_SPLIT_PATTERN.split(group)).length>1 ||"*".equals(group)){ //通过SPI加载MergeableCluster实例,并调用doRefer继续执行服务引用逻辑 returndoRefer(getMergeableCluster(),registry,type,url); } } //调用doRefer继续执行服务引用逻辑 returndoRefer(cluster,registry,type,url); } 上面代码首先为url 设置协议头,然后根据url 参数加载注册中心实例。然后获取group 配置,根据group 配置决定doRefer 第一个参数的类型。这里的重点是doRefer 方法,如下: private<T>Invoker<T>doRefer(Clustercluster,Registryregistry,Class<T> type,URLurl){ //创建RegistryDirectory实例 RegistryDirectory<T>directory=newRegistryDirectory<T>(type,url); //设置注册中心和协议 directory.setRegistry(registry); directory.setProtocol(protocol); Map<String,String>parameters=newHashMap<String,String> (directory.getUrl().getParameters()); //生成服务消费者链接 URLsubscribeUrl=newURL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY),0,type.getName(),parameters); //注册服务消费者,在consumers目录下新节点 if(!Constants.ANY_VALUE.equals(url.getServiceInterface()) &&url.getParameter(Constants.REGISTER_KEY,true)){ registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, Constants.CHECK_KEY,String.valueOf(false))); } //订阅providers、configurators、routers等节点数据 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY +","+Constants.CONFIGURATORS_CATEGORY +","+Constants.ROUTERS_CATEGORY)); //一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个 Invokerinvoker=cluster.join(directory); ProviderConsumerRegTable.registerConsumer(invoker,url,subscribeUrl, directory); returninvoker; } 如上,doRefer 方法创建一个RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在providers 产生多个节点,这个时候就需要Cluster 将多个服务节点合并为一个,并生成一个Invoker。 (3)创建代理对象 Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为ProxyFactory 的getProxy,接下来进行分析。 public<T>TgetProxy(Invoker<T>invoker)throwsRpcException{ //调用重载方法 returngetProxy(invoker,false); } public<T>TgetProxy(Invoker<T>invoker,booleangeneric)throwsRpcException{ Class<?>[]interfaces=null; //获取接口列表 Stringconfig=invoker.getUrl().getParameter("interfaces"); if(config!=null&&config.length()>0){ //切分接口列表 String[]types=Constants.COMMA_SPLIT_PATTERN.split(config); if(types!=null&&types.length>0){ interfaces=newClass<?>[types.length+2]; //设置服务接口类和EchoService.class到interfaces中 interfaces[0]=invoker.getInterface(); interfaces[1]=EchoService.class; for(inti=0;i<types.length;i++){ //加载接口类 interfaces[i+1]=ReflectUtils.forName(types[i]); } } } if(interfaces==null){ interfaces=newClass<?>[]{invoker.getInterface(),EchoService.class}; } //为http和hessian协议提供泛化调用支持,参考pullrequest#1827 if(!invoker.getInterface().equals(GenericService.class)&&generic){ intlen=interfaces.length; Class<?>[]temp=interfaces; //创建新的interfaces数组 interfaces=newClass<?>[len+1]; System.arraycopy(temp,0,interfaces,0,len); //设置GenericService.class到数组中 interfaces[len]=GenericService.class; } //调用重载方法 returngetProxy(invoker,interfaces); } publicabstract<T>TgetProxy(Invoker<T>invoker,Class<?>[]types); 如上,上面大段代码都是用来获取interfaces 数组的,我们继续往下看。getProxy(Invoker, Class<?>[]) 这个方法是一个抽象方法,下面我们到JavassistProxyFactory 类中看一下该方法的实现代码。 public<T>TgetProxy(Invoker<T>invoker,Class<?>[]interfaces){ //生成Proxy子类(Proxy是抽象类)。并调用Proxy子类的newInstance方法创建Proxy实例 return(T)Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); } 上面代码并不多,首先是通过Proxy 的getProxy 方法获取Proxy 子类,然后创建InvokerInvocationHandler 对象,并将该对象传给newInstance 生成Proxy 实例。InvokerInvocationHandler 实现JDK 的InvocationHandler 接口,具体的用途是拦截接口类调用。下面以org.apache.dubbo.demo.DemoService 这个接口为例,来看一下该接口代理类代码大致是怎样的(忽略EchoService 接口)。 packageorg.apache.dubbo.common.bytecode; publicclassproxy0implementsorg.apache.dubbo.demo.DemoService{ publicstaticjava.lang.reflect.Method[]methods; privatejava.lang.reflect.InvocationHandlerhandler; publicproxy0(){ } publicproxy0(java.lang.reflect.InvocationHandlerarg0){ handler=$1; } publicjava.lang.StringsayHello(java.lang.Stringarg0){ Object[]args=newObject[1]; args[0]=($w)$1; Objectret=handler.invoke(this,methods[0],args); return(java.lang.String)ret; } } 好了,到这里代理类生成逻辑就分析完了。整个过程比较复杂,大家需要耐心看一下。1.3 总结 从注册中心发现引用服务:在有注册中心,通过注册中心发现提供者地址的情况下,ReferenceConfig 解析出的URL 格式为: registry://registryhost:/org.apache.registry.RegistryService?refer=URL.encode("conumerhost/com.foo.FooService?version=1.0.0") 。 通过URL 的registry://协议头识别,就会调用RegistryProtocol#refer()方法 查询提供者URL,如 dubbo://service-host/com.foo.FooService?version=1.0.0 ,来获取注册中心 创建一个RegistryDirectory 实例并设置注册中心和协议 生成conusmer 连接,在consumer 目录下创建节点,向注册中心注册 注册完毕后,订阅providers,configurators,routers 等节点的数据 通过URL 的 dubbo:// 协议头识别,调用 DubboProtocol#refer() 方法,创建一个ExchangeClient 客户端并返回DubboInvoker 实例 由于一个服务可能会部署在多台服务器上,这样就会在providers 产生多个节点,这样也就会得到多个DubboInvoker 实例,就需要RegistryProtocol 调用Cluster 将多个服务提供者节点伪装成一个节点,并返回一个Invoker Invoker 创建完毕后,调用ProxyFactory 为服务接口生成代理对象,返回提供者引用 02 网络通信 在之前的内容中,我们分析了消费者端服务发现与提供者端服务暴露的相关内容,同时也知道消费者端通过内置的负载均衡算法获取合适的调用invoker进行远程调用。接下来我们再研究下远程调用过程即网络通信。 网络通信位于Remoting模块: Remoting 实现是Dubbo 协议的实现,如果你选择RMI 协议,整个Remoting 都不会用上; Remoting 内部再划为 Transport 传输层 和 Exchange 信息交换层 ; Transport 层只负责单向消息传输,是对Mina, Netty, Grizzly 的抽象,它也可以扩展UDP 传输; Exchange 层是在传输层之上封装了Request-Response 语义; 网络通信的问题: 客户端与服务端连通性问题 粘包拆包问题 异步多线程数据一致问题 2.1 通信协议 dubbo内置,dubbo协议 ,rmi协议,hessian协议,http协议,webservice协议,thrift协议,rest协议,grpc协议,memcached协议,redis协议等10种通讯协议。各个协议特点如下 dubbo协议 Dubbo 缺省协议采用单一长连接和NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。 缺省协议,使用基于mina 1.1.7 和hessian 3.2.1 的tbremoting 交互。 连接个数:单连接 连接方式:长连接 传输协议:TCP 传输方式:NIO 异步传输 序列化:Hessian 二进制序列化 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo 协议传输大文件或超大字符串。 适用场景:常规远程服务方法调用 rmi协议 RMI 协议采用JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和JDK 标准序列化方式。 连接个数:多连接 连接方式:短连接 传输协议:TCP 传输方式:同步传输 序列化:Java 标准二进制序列化 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。 适用场景:常规远程服务方法调用,与原生RMI服务互操作 hessian协议 Hessian 协议用于集成Hessian 的服务,Hessian 底层采用Http 通讯,采用Servlet 暴露服务, Dubbo 缺省内嵌Jetty 作为服务器实现。 Dubbo 的Hessian 协议可以和原生Hessian 服务互操作,即:提供者用Dubbo 的Hessian 协议暴露服务,消费者直接用标准Hessian 接口调用或者提供方用标准Hessian 暴露服务,消费方用Dubbo 的Hessian 协议调用。 连接个数:多连接 连接方式:短连接 传输协议:HTTP 传输方式:同步传输 序列化:Hessian二进制序列化 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。 适用场景:页面传输,文件传输,或与原生hessian服务互操作 http协议 基于HTTP 表单的远程调用协议,采用Spring 的HttpInvoker 实现 连接个数:多连接 连接方式:短连接 传输协议:HTTP 传输方式:同步传输 序列化:表单序列化 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。 适用场景:需同时给应用程序和浏览器JS 使用的服务。 webservice协议基于WebService 的远程调用协议,基于Apache CXF 实现](http://dubbo.apache.org/zh-cn/docs/user/references/protocol/webservice.html#fn2)。 可以和原生WebService 服务互操作,即:提供者用Dubbo 的WebService 协议暴露服务,消费者直接用标准WebService 接口调用,或者提供方用标准WebService 暴露服务,消费方用Dubbo 的WebService 协议调用。 连接个数:多连接 连接方式:短连接 传输协议:HTTP 传输方式:同步传输 序列化:SOAP 文本序列化(http + xml) 适用场景:系统集成,跨语言调用 thrift协议 当前dubbo 支持[1]的thrift 协议是对thrift 原生协议[2] 的扩展,在原生协议的基础上添加了一些额外的头信息,比如service name,magic number 等。 rest协议 基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持 grpc协议 Dubbo 自2.7.5 版本开始支持gRPC 协议,对于计划使用HTTP/2 通信,或者想利用gRPC 带来的Stream、反压、Reactive 编程等能力的开发者来说, 都可以考虑启用gRPC 协议。 为期望使用gRPC 协议的用户带来服务治理能力,方便接入Dubbo 体系用户可以使用Dubbo 风格的,基于接口的编程风格来定义和使用远程服务 memcached协议 基于memcached实现的RPC 协议 redis协议 基于Redis 实现的RPC 协议2.2 序列化 序列化就是将对象转成字节流,用于网络传输,以及将字节流转为对象,用于在收到字节流数据后还原成对象。序列化的优势有很多,例如安全性更好、可跨平台等。我们知道dubbo基于netty进行网络通讯,在NettyClient.doOpen() 方法中可以看到Netty的相关类 bootstrap.setPipelineFactory(newChannelPipelineFactory(){ publicChannelPipelinegetPipeline(){ NettyCodecAdapteradapter=newNettyCodecAdapter(getCodec(),getUrl(), NettyClient.this); ChannelPipelinepipeline=Channels.pipeline(); pipeline.addLast("decoder",adapter.getDecoder()); pipeline.addLast("encoder",adapter.getEncoder()); pipeline.addLast("handler",nettyHandler); returnpipeline; } }); 然后去看NettyCodecAdapter 类最后进入ExchangeCodec类的encodeRequest方法,如下: protectedvoidencodeRequest(Channelchannel,ChannelBufferbuffer,Request req)throwsIOException{ Serializationserialization=getSerialization(channel); //header. byte[]header=newbyte[HEADER_LENGTH]; 是的,就是Serialization接口,默认是Hessian2Serialization序列化接口。 Dubbo序列化支持java、compactedjava、nativejava、fastjson、dubbo、fst、hessian2、kryo,protostuff其中默认hessian2。其中java、compactedjava、nativejava属于原生java的序列化。 dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它。hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的,它是dubbo RPC默认启用的序列化方式。json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。2.3 网络通信 (1)Dubbo中的数据格式 解决socket中数据粘包拆包问题,一般有三种方式 定长协议(数据包长度一致) 定长的协议是指协议内容的长度是固定的,比如协议byte长度是50,当从网络上读取50个byte后,就进行decode解码操作。定长协议在读取或者写入时,效率比较高,因为数据缓存的大小基本都确定了,就好比数组一样,缺陷就是适应性不足,以RPC场景为例,很难估计出定长的长度是多少。 特殊结束符(数据尾:通过特殊的字符标识#) 相比定长协议,如果能够定义一个特殊字符作为每个协议单元结束的标示,就能够以变长的方式进行通信,从而在数据传输和高效之间取得平衡,比如用特殊字符 \n 。特殊结束符方式的问题是过于简单的思考了协议传输的过程,对于一个协议单元必须要全部读入才能够进行处理,除此之外必须要防止用户传输的数据不能同结束符相同,否则就会出现紊乱。 变长协议(协议头+payload模式) 这种一般是自定义协议,会以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度。dubbo就是使用这种形式的数据传输格式 Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容。 偏移量(Bit) 字段 取值 0 ~ 7 魔数高位 0xda00 8 ~ 15 魔数低位 0xbb 16 数据包类型 0 - Response, 1 - Request 17 调用方式 仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用 18 事件标 识 0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包 19 ~23 序列化器编号 2 - Hessian2Serialization3 - JavaSerialization4 - CompactedJavaSerialization6 - FastJsonSerialization7 - NativeJavaSerialization8 - KryoSerialization9 - FstSerialization 24 ~31 状态 20 - OK 30 - CLIENT_TIMEOUT 31 - SERVER_TIMEOUT 40 -BAD_REQUEST 50 - BAD_RESPONSE ...... 32 ~95 请求编号 共8字节,运行时生成 96 ~127 消息体长度 运行时计算 (2)消费端发送请求 /** *proxy0#sayHello(String) *—>InvokerInvocationHandler#invoke(Object,Method,Object[]) *—>MockClusterInvoker#invoke(Invocation) *—>AbstractClusterInvoker#invoke(Invocation) *—>FailoverClusterInvoker#doInvoke(Invocation,List<Invoker<T>>,LoadBalance) *—>Filter#invoke(Invoker,Invocation)//包含多个Filter调用 *—>ListenerInvokerWrapper#invoke(Invocation) *—>AbstractInvoker#invoke(Invocation) *—>DubboInvoker#doInvoke(Invocation) *—>ReferenceCountExchangeClient#request(Object,int) *—>HeaderExchangeClient#request(Object,int) *—>HeaderExchangeChannel#request(Object,int) *—>AbstractPeer#send(Object) *—>AbstractClient#send(Object,boolean) *—>NettyChannel#send(Object,boolean) *—>NioClientSocketChannel#write(Object) */ dubbo消费方,自动生成代码对象如下 publicclassproxy0implementsClassGenerator.DC,EchoService,DemoService{ privateInvocationHandlerhandler; publicStringsayHello(Stringstring){ //将参数存储到Object数组中 Object[]arrobject=newObject[]{string}; //调用InvocationHandler实现类的invoke方法得到调用结果 Objectobject=this.handler.invoke(this,methods[0],arrobject); //返回调用结果 return(String)object; } } InvokerInvocationHandler 中的invoker 成员变量类型为MockClusterInvoker,MockClusterInvoker内部封装了服务降级逻辑。下面简单看一下: publicResultinvoke(Invocationinvocation)throwsRpcException{ Resultresult=null; //获取mock配置值 Stringvalue= directory.getUrl().getMethodParameter(invocation.getMethodName(),MOCK_KEY, Boolean.FALSE.toString()).trim(); if(value.length()==0||value.equalsIgnoreCase("false")){ //无mock逻辑,直接调用其他Invoker对象的invoke方法, //比如FailoverClusterInvoker result=this.invoker.invoke(invocation); }elseif(value.startsWith("force")){ //force:xxx直接执行mock逻辑,不发起远程调用 result=doMockInvoke(invocation,null); }else{ //fail:xxx表示消费方对调用服务失败后,再执行mock逻辑,不抛出异常 try{ result=this.invoker.invoke(invocation); }catch(RpcExceptione){ //调用失败,执行mock逻辑 result=doMockInvoke(invocation,e); } } returnresult; } 考虑到前文已经详细分析过FailoverClusterInvoker,因此本节略过FailoverClusterInvoker,直接分析DubboInvoker。 publicabstractclassAbstractInvoker<T>implementsInvoker<T>{ publicResultinvoke(Invocationinv)throwsRpcException{ if(destroyed.get()){ thrownewRpcException("Rpcinvokerforservice..."); } RpcInvocationinvocation=(RpcInvocation)inv; //设置Invoker invocation.setInvoker(this); if(attachment!=null&&attachment.size()>0){ //设置attachment invocation.addAttachmentsIfAbsent(attachment); } Map<String,String>contextAttachments= RpcContext.getContext().getAttachments(); if(contextAttachments!=null&&contextAttachments.size()!=0){ //添加contextAttachments到RpcInvocation#attachment变量中 invocation.addAttachments(contextAttachments); } if(getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY,false)){ //设置异步信息到RpcInvocation#attachment中 invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); } RpcUtils.attachInvocationIdIfAsync(getUrl(),invocation); try{ //抽象方法,由子类实现 returndoInvoke(invocation); }catch(InvocationTargetExceptione){ //... }catch(RpcExceptione){ //... }catch(Throwablee){ returnnewRpcResult(e); } } protectedabstractResultdoInvoke(Invocationinvocation)throwsThrowable; //省略其他方法 } 上面的代码来自AbstractInvoker 类,其中大部分代码用于添加信息到RpcInvocation#attachment 变量中,添加完毕后,调用doInvoke 执行后续的调用。doInvoke 是一个抽象方法,需要由子类实现,下面到DubboInvoker 中看一下。 @Override protectedResultdoInvoke(finalInvocationinvocation)throwsThrowable{ RpcInvocationinv=(RpcInvocation)invocation; finalStringmethodName=RpcUtils.getMethodName(invocation); //将目标方法以及版本号作为参数放入到Invocation中 inv.setAttachment(PATH_KEY,getUrl().getPath()); inv.setAttachment(VERSION_KEY,version); //获得客户端连接 ExchangeClientcurrentClient;//初始化invoker的时候,构建的一个远程通信连接 if(clients.length==1){//默认 currentClient=clients[0]; }else{ //通过取模获得其中一个连接 currentClient=clients[index.getAndIncrement()%clients.length]; } try{ //表示当前的方法是否存在返回值 booleanisOneway=RpcUtils.isOneway(getUrl(),invocation); inttimeout=getUrl().getMethodParameter(methodName,TIMEOUT_KEY, DEFAULT_TIMEOUT); //isOneway为true,表示“单向”通信 if(isOneway){//异步无返回值 booleanisSent=getUrl().getMethodParameter(methodName, Constants.SENT_KEY,false); currentClient.send(inv,isSent); RpcContext.getContext().setFuture(null); returnAsyncRpcResult.newDefaultAsyncResult(invocation); }else{//存在返回值 //是否采用异步 AsyncRpcResultasyncRpcResult=newAsyncRpcResult(inv); CompletableFuture<Object>responseFuture= currentClient.request(inv,timeout); responseFuture.whenComplete((obj,t)->{ if(t!=null){ asyncRpcResult.completeExceptionally(t); }else{ asyncRpcResult.complete((AppResponse)obj); } }); RpcContext.getContext().setFuture(new FutureAdapter(asyncRpcResult)); returnasyncRpcResult; } } //省略无关代码 } 最终进入到HeaderExchangeChannel#request方法,拼装Request并将请求发送出去 publicCompletableFuture<Object>request(Objectrequest,inttimeout)throws RemotingException{ if(closed){ thrownewRemotingException(this.getLocalAddress(),null,"Failed tosendrequest"+request+",cause:Thechannel"+this+"isclosed!"); } //创建请求对象 Requestreq=newRequest(); req.setVersion(Version.getProtocolVersion()); req.setTwoWay(true); req.setData(request); DefaultFuturefuture=DefaultFuture.newFuture(channel,req,timeout); try{ //NettyClient channel.send(req); }catch(RemotingExceptione){ future.cancel(); throwe; } returnfuture; } 请求编码如何做的? 在netty启动时,我们设置了编解码器,其中通过ExchangeCodec完成编解码工作如下: publicclassExchangeCodecextendsTelnetCodec{ //消息头长度 protectedstaticfinalintHEADER_LENGTH=16; //魔数内容 protectedstaticfinalshortMAGIC=(short)0xdabb; protectedstaticfinalbyteMAGIC_HIGH=Bytes.short2bytes(MAGIC)[0]; protectedstaticfinalbyteMAGIC_LOW=Bytes.short2bytes(MAGIC)[1]; protectedstaticfinalbyteFLAG_REQUEST=(byte)0x80; protectedstaticfinalbyteFLAG_TWOWAY=(byte)0x40; protectedstaticfinalbyteFLAG_EVENT=(byte)0x20; protectedstaticfinalintSERIALIZATION_MASK=0x1f; privatestaticfinalLoggerlogger= LoggerFactory.getLogger(ExchangeCodec.class); publicShortgetMagicCode(){ returnMAGIC; } @Override publicvoidencode(Channelchannel,ChannelBufferbuffer,Objectmsg)throws IOException{ if(msginstanceofRequest){ //对Request对象进行编码 encodeRequest(channel,buffer,(Request)msg); }elseif(msginstanceofResponse){ //对Response对象进行编码,后面分析 encodeResponse(channel,buffer,(Response)msg); }else{ super.encode(channel,buffer,msg); } } protectedvoidencodeRequest(Channelchannel,ChannelBufferbuffer,Request req)throwsIOException{ Serializationserialization=getSerialization(channel); //创建消息头字节数组,长度为16 byte[]header=newbyte[HEADER_LENGTH]; //设置魔数 Bytes.short2bytes(MAGIC,header); //设置数据包类型(Request/Response)和序列化器编号 header[2]=(byte)(FLAG_REQUEST|serialization.getContentTypeId()); //设置通信方式(单向/双向) if(req.isTwoWay()){ header[2]|=FLAG_TWOWAY; } //设置事件标识 if(req.isEvent()){header[2]|=FLAG_EVENT; } //设置请求编号,8个字节,从第4个字节开始设置 Bytes.long2bytes(req.getId(),header,4); //获取buffer当前的写位置 intsavedWriteIndex=buffer.writerIndex(); //更新writerIndex,为消息头预留16个字节的空间 buffer.writerIndex(savedWriteIndex+HEADER_LENGTH); ChannelBufferOutputStreambos=newChannelBufferOutputStream(buffer); //创建序列化器,比如Hessian2ObjectOutput ObjectOutputout=serialization.serialize(channel.getUrl(),bos); if(req.isEvent()){ //对事件数据进行序列化操作 encodeEventData(channel,out,req.getData()); }else{ //对请求数据进行序列化操作 encodeRequestData(channel,out,req.getData(),req.getVersion()); } out.flushBuffer(); if(outinstanceofCleanable){ ((Cleanable)out).cleanup(); } bos.flush(); bos.close(); //获取写入的字节数,也就是消息体长度 intlen=bos.writtenBytes(); checkPayload(channel,len); //将消息体长度写入到消息头中 Bytes.int2bytes(len,header,12); //将buffer指针移动到savedWriteIndex,为写消息头做准备 buffer.writerIndex(savedWriteIndex); //从savedWriteIndex下标处写入消息头 buffer.writeBytes(header); //设置新的writerIndex,writerIndex=原写下标+消息头长度+消息体长度 buffer.writerIndex(savedWriteIndex+HEADER_LENGTH+len); } //省略其他方法 } 以上就是请求对象的编码过程,该过程首先会通过位运算将消息头写入到header 数组中。然后对Request 对象的data 字段执行序列化操作,序列化后的数据最终会存储到ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度len,紧接着将len 写入到header 指定位置处。最后再将消息头字节数组header 写入到ChannelBuffer 中,整个编码过程就结束了。本节的最后,我们再来看一下Request 对象的data 字段序列化过程,也就是encodeRequestData 方法的逻辑,如下: publicclassDubboCodecextendsExchangeCodecimplementsCodec2{ protectedvoidencodeRequestData(Channelchannel,ObjectOutputout,Object data,Stringversion)throwsIOException{ RpcInvocationinv=(RpcInvocation)data; //依次序列化dubboversion、path、version out.writeUTF(version); out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); //序列化调用方法名 out.writeUTF(inv.getMethodName()); //将参数类型转换为字符串,并进行序列化 out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); Object[]args=inv.getArguments(); if(args!=null) for(inti=0;i<args.length;i++){ //对运行时参数进行序列化 out.writeObject(encodeInvocationArgument(channel,inv,i)); } //序列化attachments out.writeObject(inv.getAttachments()); } } 至此,关于服务消费方发送请求的过程就分析完了,接下来我们来看一下服务提供方是如何接收请求的。 (3)提供方接受请求 请求如何解码? 这里直接分析请求数据的解码逻辑,忽略中间过程,如下: publicclassExchangeCodecextendsTelnetCodec{ @Override publicObjectdecode(Channelchannel,ChannelBufferbuffer)throws IOException{ intreadable=buffer.readableBytes(); //创建消息头字节数组 byte[]header=newbyte[Math.min(readable,HEADER_LENGTH)]; //读取消息头数据 buffer.readBytes(header); //调用重载方法进行后续解码工作 returndecode(channel,buffer,readable,header); } @Override protectedObjectdecode(Channelchannel,ChannelBufferbuffer,intreadable, byte[]header)throwsIOException{ //检查魔数是否相等 if(readable>0&&header[0]!=MAGIC_HIGH ||readable>1&&header[1]!=MAGIC_LOW){ intlength=header.length; if(header.length<readable){ header=Bytes.copyOf(header,readable); buffer.readBytes(header,length,readable-length); } for(inti=1;i<header.length-1;i++){ if(header[i]==MAGIC_HIGH&&header[i+1]==MAGIC_LOW){ buffer.readerIndex(buffer.readerIndex()-header.length+ i); header=Bytes.copyOf(header,i); break; } } //通过telnet命令行发送的数据包不包含消息头,所以这里 //调用TelnetCodec的decode方法对数据包进行解码 returnsuper.decode(channel,buffer,readable,header); } //检测可读数据量是否少于消息头长度,若小于则立即返回 DecodeResult.NEED_MORE_INPUT if(readable<HEADER_LENGTH){ returnDecodeResult.NEED_MORE_INPUT; } //从消息头中获取消息体长度 intlen=Bytes.bytes2int(header,12); //检测消息体长度是否超出限制,超出则抛出异常 checkPayload(channel,len); inttt=len+HEADER_LENGTH; //检测可读的字节数是否小于实际的字节数 if(readable<tt){ returnDecodeResult.NEED_MORE_INPUT; } ChannelBufferInputStreamis=newChannelBufferInputStream(buffer,len); try{ //继续进行解码工作 returndecodeBody(channel,is,header); }finally{ if(is.available()>0){ try{ StreamUtils.skipUnusedStream(is); }catch(IOExceptione){ logger.warn(e.getMessage(),e); } } } } } 上面方法通过检测消息头中的魔数是否与规定的魔数相等,提前拦截掉非常规数据包,比如通过telnet命令行发出的数据包。接着再对消息体长度,以及可读字节数进行检测。最后调用decodeBody 方法进行后续的解码工作,ExchangeCodec 中实现了decodeBody 方法,但因其子类DubboCodec 覆写了该方法,所以在运行时DubboCodec 中的decodeBody 方法会被调用。下面我们来看一下该方法的代码。 publicclassDubboCodecextendsExchangeCodecimplementsCodec2{ @Override protectedObjectdecodeBody(Channelchannel,InputStreamis,byte[]header) throwsIOException{ //获取消息头中的第三个字节,并通过逻辑与运算得到序列化器编号byteflag=header[2],proto=(byte)(flag&SERIALIZATION_MASK); Serializations=CodecSupport.getSerialization(channel.getUrl(), proto); //获取调用编号 longid=Bytes.bytes2long(header,4); //通过逻辑与运算得到调用类型,0-Response,1-Request if((flag&FLAG_REQUEST)==0){ //对响应结果进行解码,得到Response对象。这个非本节内容,后面再分析 //... }else{ //创建Request对象 Requestreq=newRequest(id); req.setVersion(Version.getProtocolVersion()); //通过逻辑与运算得到通信方式,并设置到Request对象中 req.setTwoWay((flag&FLAG_TWOWAY)!=0); //通过位运算检测数据包是否为事件类型 if((flag&FLAG_EVENT)!=0){ //设置心跳事件到Request对象中 req.setEvent(Request.HEARTBEAT_EVENT); } try{ Objectdata; if(req.isHeartbeat()){ //对心跳包进行解码,该方法已被标注为废弃 data=decodeHeartbeatData(channel,deserialize(s, channel.getUrl(),is)); }elseif(req.isEvent()){ //对事件数据进行解码 data=decodeEventData(channel,deserialize(s, channel.getUrl(),is)); }else{ DecodeableRpcInvocationinv; //根据url参数判断是否在IO线程上对消息体进行解码 if(channel.getUrl().getParameter( Constants.DECODE_IN_IO_THREAD_KEY, Constants.DEFAULT_DECODE_IN_IO_THREAD)){ inv=newDecodeableRpcInvocation(channel,req,is, proto); //在当前线程,也就是IO线程上进行后续的解码工作。此工作完成后, 可将 //调用方法名、attachment、以及调用参数解析出来 inv.decode(); }else{ //仅创建DecodeableRpcInvocation对象,但不在当前线程上执行解 码逻辑 inv=newDecodeableRpcInvocation(channel,req, new UnsafeByteArrayInputStream(readMessageData(is)),proto); } data=inv; } //设置data到Request对象中 req.setData(data); }catch(Throwablet){ //若解码过程中出现异常,则将broken字段设为true, //并将异常对象设置到Reqeust对象中 req.setBroken(true); req.setData(t); } returnreq; } } } 如上,decodeBody 对部分字段进行了解码,并将解码得到的字段封装到Request 中。随后会调用DecodeableRpcInvocation 的decode 方法进行后续的解码工作。此工作完成后,可将调用方法名、attachment、以及调用参数解析出来。 调用服务 解码器将数据包解析成Request 对象后,NettyHandler 的messageReceived 方法紧接着会收到这个对象,并将这个对象继续向下传递。整个调用栈如下: NettyServerHandler#channelRead(ChannelHandlerContext,MessageEvent) —>AbstractPeer#received(Channel,Object) —>MultiMessageHandler#received(Channel,Object) —>HeartbeatHandler#received(Channel,Object) —>AllChannelHandler#received(Channel,Object) —>ExecutorService#execute(Runnable)//由线程池执行后续的调用逻辑= 这里我们直接分析调用栈中的分析第一个和最后一个调用方法逻辑。如下:考虑到篇幅,以及很多中间调用的逻辑并非十分重要,所以这里就不对调用栈中的每个方法都进行分析了。这里我们直接分析最后一个调用方法逻辑。如下: publicclassChannelEventRunnableimplementsRunnable{ privatefinalChannelHandlerhandler; privatefinalChannelchannel; privatefinalChannelStatestate; privatefinalThrowableexception; privatefinalObjectmessage; @Override publicvoidrun(){ //检测通道状态,对于请求或响应消息,此时state=RECEIVED if(state==ChannelState.RECEIVED){ try{ //将channel和message传给ChannelHandler对象,进行后续的调用 handler.received(channel,message); }catch(Exceptione){ logger.warn("...operationerror,channelis...messageis ..."); } } //其他消息类型通过switch进行处理 else{ switch(state){ caseCONNECTED: try{ handler.connected(channel); }catch(Exceptione){ logger.warn("...operationerror,channelis..."); } break; caseDISCONNECTED: //... caseSENT: //... caseCAUGHT: //... default: logger.warn("unknownstate:"+state+",messageis"+ message); } } } } 如上,请求和响应消息出现频率明显比其他类型消息高,所以这里对该类型的消息进行了针对性判断ChannelEventRunnable 仅是一个中转站,它的run 方法中并不包含具体的调用逻辑,仅用于将参数传给其他ChannelHandler 对象进行处理,该对象类型为DecodeHandler publicclassDecodeHandlerextendsAbstractChannelHandlerDelegate{ publicDecodeHandler(ChannelHandlerhandler){ super(handler); } @Override publicvoidreceived(Channelchannel,Objectmessage)throws RemotingException{ if(messageinstanceofDecodeable){ //对Decodeable接口实现类对象进行解码 decode(message); } if(messageinstanceofRequest){ //对Request的data字段进行解码 decode(((Request)message).getData()); } if(messageinstanceofResponse){ //对Request的result字段进行解码 decode(((Response)message).getResult()); } //执行后续逻辑 handler.received(channel,message); } privatevoiddecode(Objectmessage){ //Decodeable接口目前有两个实现类, //分别为DecodeableRpcInvocation和DecodeableRpcResult if(message!=null&&messageinstanceofDecodeable){ try{ //执行解码逻辑 ((Decodeable)message).decode(); }catch(Throwablee){ if(log.isWarnEnabled()){ log.warn("CallDecodeable.decodefailed:"+e.getMessage(), e); } } } } } DecodeHandler 主要是包含了一些解码逻辑,完全解码后的Request 对象会继续向后传递 publicclassDubboProtocolextendsAbstractProtocol{ publicstaticfinalStringNAME="dubbo"; privateExchangeHandlerrequestHandler=newExchangeHandlerAdapter(){ @Override publicObjectreply(ExchangeChannelchannel,Objectmessage)throws RemotingException{ if(messageinstanceofInvocation){ Invocationinv=(Invocation)message; //获取Invoker实例 Invoker<?>invoker=getInvoker(channel,inv); if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INV OKE))){ //回调相关,忽略 } RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); //通过Invoker调用具体的服务 returninvoker.invoke(inv); } thrownewRemotingException(channel,"Unsupportedrequest:..."); } //忽略其他方法 } Invoker<?>getInvoker(Channelchannel,Invocationinv)throws RemotingException{ //忽略回调和本地存根相关逻辑 //... intport=channel.getLocalAddress().getPort(); //计算servicekey,格式为groupName/serviceName:serviceVersion:port。比 如: //dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880 StringserviceKey=serviceKey(port,path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY)); //从exporterMap查找与serviceKey相对应的DubboExporter对象, //服务导出过程中会将<serviceKey,DubboExporter>映射关系存储到exporterMap 集合中 DubboExporter<?>exporter=(DubboExporter<?>) exporterMap.get(serviceKey); if(exporter==null) thrownewRemotingException(channel,"Notfoundexportedservice ..."); //获取Invoker对象,并返回 returnexporter.getInvoker(); } //忽略其他方法 } 在之前课程中介绍过,服务全部暴露完成之后保存到exporterMap中。这里就是通过serviceKey获取exporter之后获取Invoker,并通过Invoker 的invoke 方法调用服务逻辑 publicabstractclassAbstractProxyInvoker<T>implementsInvoker<T>{ @Override publicResultinvoke(Invocationinvocation)throwsRpcException{ try{ //调用doInvoke执行后续的调用,并将调用结果封装到RpcResult中,并 returnnewRpcResult(doInvoke(proxy,invocation.getMethodName(), invocation.getParameterTypes(),invocation.getArguments())); }catch(InvocationTargetExceptione){ returnnewRpcResult(e.getTargetException()); }catch(Throwablee){ thrownewRpcException("Failedtoinvokeremoteproxymethod..."); } } protectedabstractObjectdoInvoke(Tproxy,StringmethodName,Class<?>[] parameterTypes,Object[]arguments)throwsThrowable; } 如上,doInvoke 是一个抽象方法,这个需要由具体的Invoker 实例实现。Invoker 实例是在运行时通过JavassistProxyFactory 创建的,创建逻辑如下: publicclassJavassistProxyFactoryextendsAbstractProxyFactory{ //省略其他方法 @Override public<T>Invoker<T>getInvoker(Tproxy,Class<T>type,URLurl){ finalWrapperwrapper= Wrapper.getWrapper(proxy.getClass().getName().indexOf('$')<0? proxy.getClass():type); //创建匿名类对象 returnnewAbstractProxyInvoker<T>(proxy,type,url){ @Override protectedObjectdoInvoke(Tproxy,StringmethodName, Class<?>[]parameterTypes, Object[]arguments)throwsThrowable{ //调用invokeMethod方法进行后续的调用 returnwrapper.invokeMethod(proxy,methodName,parameterTypes, arguments); } }; } } Wrapper 是一个抽象类,其中invokeMethod 是一个抽象方法。Dubbo 会在运行时通过Javassist 框架为Wrapper 生成实现类,并实现invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以DemoServiceImpl 为例,Javassist 为其生成的代理类如下。 /**Wrapper0是在运行时生成的,大家可使用Arthas进行反编译*/ publicclassWrapper0extendsWrapperimplementsClassGenerator.DC{ publicstaticString[]pns; publicstaticMappts; publicstaticString[]mns; publicstaticString[]dmns; publicstaticClass[]mts0; //省略其他方法 publicObjectinvokeMethod(Objectobject,Stringstring,Class[]arrclass, Object[]arrobject)throwsInvocationTargetException{ DemoServicedemoService; try{ //类型转换 demoService=(DemoService)object; } catch(Throwablethrowable){ thrownewIllegalArgumentException(throwable); } try{ //根据方法名调用指定的方法 if("sayHello".equals(string)&&arrclass.length==1){ returndemoService.sayHello((String)arrobject[0]); } } catch(Throwablethrowable){ thrownewInvocationTargetException(throwable); } thrownewNoSuchMethodException(newStringBuffer().append("Notfound method\"").append(string).append("\"inclass com.alibaba.dubbo.demo.DemoService.").toString()); } } 到这里,整个服务调用过程就分析完了。最后把调用过程贴出来,如下: ChannelEventRunnable#run() —>DecodeHandler#received(Channel,Object) —>HeaderExchangeHandler#received(Channel,Object) —>HeaderExchangeHandler#handleRequest(ExchangeChannel,Request) —>DubboProtocol.requestHandler#reply(ExchangeChannel,Object) —>Filter#invoke(Invoker,Invocation) —>AbstractProxyInvoker#invoke(Invocation) —>Wrapper0#invokeMethod(Object,String,Class[],Object[]) —>DemoServiceImpl#sayHello(String) (4)提供方返回调用结果 服务提供方调用指定服务后,会将调用结果封装到Response 对象中,并将该对象返回给服务消费方。服务提供方也是通过NettyChannel 的send 方法将Response 对象返回,这里就不在重复分析了。 (5)消费方接收调用结果 服务消费方在收到响应数据后,首先要做的事情是对响应数据进行解码,得到Response 对象。然后再将该对象传递给下一个入站处理器,这个入站处理器就是NettyHandler。接下来NettyHandler 会将这个对象继续向下传递,最后AllChannelHandler 的received 方法会收到这个对象,并将这个对象派发到线程池中。这个过程和服务提供方接收请求的过程是一样的,因此这里就不重复分析了03 小结 至此整个dubbo的核心流程原理及其源码,我们就分析完毕了,整体流程思路不复杂,但是细节很多,要先理解其思想,还是得多花时间再仔细撸一遍。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册