首页> 搜索结果页
"访问storage域名" 检索
共 677 条结果
阿里云满减优惠券,适用于阿里云250种云产品,领取及使用介绍
阿里云满减优惠券也称上云礼包,是一种满减抵扣性质的优惠券,只要用户领取之后,购买阿里云产品时订单金额达到优惠券的满减抵扣条件即可使用优惠券获得相应金额的满减优惠,目前满减优惠券最低可抵扣20元,最高可抵扣1000元,而且最重要的,满减优惠券不仅适用于阿里云服务器类产品,同时也适用于其他阿里云官方自营的各种云产品,根据官方公布的规则,有250种阿里云产品都支持购买时使用满减优惠券。一、满减优惠券在哪里领取满减优惠券一般在阿里云大促活动和官方领券中心都可以领取,用户可随时关注官方领券中心,这里会实时公布最新的满减优惠券金额及使用条件,同时还会公布其他种类的一些优惠券,例如目前就有OceanBase新用户满减9折优惠券、对象存储满减优惠券、云服务器ECS与轻量应用服务器续费优惠券等可以领取。优惠券实时种类及金额参考:阿里云领券中心进入领券中心之后,点击优惠券下面的立即领取即可。领取之后,在我们购买阿里云产品的时,到最后一步支付订单的时候,如果我们订单金额达到满减优惠券的使用条件即可使用优惠券获得对应金额的减免,使用效果如下图所示:二、满减优惠券如何使用?新用户满减优惠券仅限新购/升级阿里云产品时使用,我们领取之后,只需要在购买或升级阿里云产品时勾选使用优惠券即可获得对应的满减优惠,系统会根据订单金额自动匹配可使用的优惠券金额,使用效果图如下:续费优惠券仅用于我们续费云服务器ECS或轻量产品时使用,我们需要续费的时候,在控制台-订单管理-续费管理里面可以查看到当下可续费的云服务器实例ID/实例名称、地域、实例状态、倒计时、付费方式、开始/结束时间(UTC+8)等信息,点击后面的续费按钮即可续费云服务器,并在支付订单的时候,勾选续费优惠券即可,如下图所示:三、满减优惠券活动规则1、活动期内,同一用户有一次领券机会,用户在活动页面点击“领取”可以一次性获得所有档位满减券。满减券发放至用户登录账号,可登录阿里云控制台,页面顶端进入费用,选择卡券管理-优惠券管理进行查询。2、满减券档位分类如下:① 商品订单满300减20元优惠券;② 商品订单满500减35元优惠券;③ 商品订单满800减60元优惠券;④ 商品订单满1000减80元优惠券;⑤ 商品订单满1500减120元优惠券;⑥ 商品订单满2000减160元优惠券;⑦ 商品订单满3000减240元优惠券;⑧ 商品订单满5000减500元优惠券;⑨ 商品订单满10000减1000元优惠券;3、优惠券使用范围:用户购买阿里云自营的指定云产品,仅限新购云产品、升级云产品。不可用于续费,不可用于域名、虚机、云市场产品,仅限时长为1年及以下的订单使用。商品订单有效金额满足满减额度,即可使用满减优惠券。每个订单仅可使用1张满减优惠券,可与指定产品折扣优惠叠加,但低于3折及新人专享价的产品不可与优惠券叠加使用。4、适用满减优惠券产品(详见下文“满减优惠券适用产品列表”)5、满减优惠券不可用于购买产品提货券。6、用户参加活动所购买的相关产品及所获得的相应权益,仅限本账号使用,不得转让、出售或以其他方式换取利益。7、本活动不与其他满减优惠券活动叠加使用。8、使用满减优惠券购买的订单退订后,满减优惠券不支持退回。三、满减优惠券适用产品列表(共计250种)1、 性能测试按量抵扣资源包 pts_bag_dp_cn2、 前端监控专家版资源包 arms_web_bag3、 应用监控资源包 arms_app_bag4、 云拨测(包年包月) arms_syntheticpre_public_cn5、 预付费包年包月Grafana服务 grafana_prepaid_public_cn6、 Prometheus服务-专家版(包年包月) prometheus_prepay_public_cn7、 性能测试包年包月 pts_prepay_public_cn8、 容器服务ACK资源包 csk_ackpro_dp_cn9、 消息队列Kafka(包年包月) alikafka_pre10、 微消息队列MQTT版(包年包月) onsMqtt11、 消息队列RabbitMQ版包年包月 ons_onsproxy_pre12、 RocketMQ4.0标准版专用资源包(包年包月) ons_pack13、 RocketMQ4.0标准版专用资源包(容量递减) onsbag14、 RocketMQ4.0企业铂金版 onspre15、 消息服务资源包(包月) mnsbag16、 RocketMQ5.0版(包年包月) ons_rmqsub_public_cn17、 性能测试PTS资源包 ptsbag18、 全链路压测专属 pts_isolation_public_cn19、 容器镜像服务企业版 acr_ee_public_cn20、 云原生网关(包年包月) mse_ingresspre_public_cn21、 微服务治理资源包(按量抵扣) mse_basic_dp_cn22、 微服务引擎MSE(包年包月) mse_prepaid_public_cn23、 企业级分布式应用服务EDAS(包年包月) prepaid_edas24、 共享流量包 flowbag25、 传统型负载均衡CLB(包年包月) slbpre26、 全球加速_实例规格(包年包月) ga_gapluspre_public_cn27、 全球加速_基础带宽包(包年包月) ga_plusbwppre_public_cn28、 云企业网(预付费) cbn_bwp_pre29、 智能接入网关带宽 smartag_b30、 智能接入网关APP smartag_s31、 智能接入网关APP流量包 smartag_s_bag32、 智能接入网关vCPE软件版 smartag_v_public_cn33、 VPN网关(包月) vpn_pre34、 EIP(包月) eip_pre35、 共享带宽(包月) cbwp_pre36、 NAT网关资源包 nat_cubag_dp_cn37、 应用加速带宽包 smartag_accelartebwp_public_cn38、 ALB资源包 slb_albcubag_dp_cn39、 5G高速上云服务连接功能费资源包 uis_cc5gconnection_dp_cn40、 5G高速上云服务流量处理费资源包 uis_cc5gflowpackage_dp_cn41、 PolarDB计算包 polardb_cu_public_cn42、 PolarDB-X2.0只读(包年包月) drds_polarxropre_public_cn43、 PolarDB存储包 polardb_package44、 云数据库PolarDB-包年包月 polardb_sub45、 PolarDB-X1.0计算层(包年包月) drdspre46、 PolarDB-X1.0存储层-私有定制RDS(包年包月) drds_mysqlpre_public_cn47、 PolarDB-X2.0(包年包月) drds_polarxpre_public_cn48、 云原生多模数据库Lindorm(包年包月) hitsdb_lindormpre_public_cn49、 时空数据库 hitsdb_spatialpre50、 云原生多模数据库LindormServerless版本 hitsdb_lindormserverlesspre_public_cn51、 时序数据库InfluxDB®版 hitsdb_influxdb_pre52、 时序数据库TSDB版(包年包月) hitsdbpre53、 Prometheus®(免费公测) hitsdb_prometheus_pre54、 Lindorm单节点预付费,仅用于开发测试和学习,请勿用于生产和性能压测! hitsdb_lindormsinglepre_public_cn55、 云原生数据仓库PostgreSQL版(包年包月) GreenplumPre56、 云原生数据仓库AnalyticDBMySQL版(包年包月) ads_pre57、 分析型数据库(包年包月) prepaid_ads58、 ADB弹性存储资源包(ads_storage_bag) ads_storage_bag59、 DataLakeAnalyticsCU时资源包 openanalytics_cu_bag60、 DataLakeAnalytics流量包 openanalytics_post61、 Spark独享版包年包月 openanalytics_dlasparkpre_public_cn62、 数据湖分析CU版(包年包月) openanalytics_dlacupre_public_cn63、 ClickHouse包年包月 clickhouse_pre_public_cn64、 云数据库KVStore版(包月) prepaid_kvstore65、 Tair包年包月 kvstore_pretair_public_cn66、 云数据库Redis包年包月(云盘版) kvstore_prepaid_public_cn67、 Redis/Tair带宽扩容(包年包月) kvstore_bandwidth_public_cn68、 Redis资源包 kvstore_localcu_dp_cn69、 kvstore_xianxia kvstore_xianxia70、 云数据库Cassandra版(包年包月) cds_pre_public_cn71、 云数据库HBase版 hbasepre72、 HBASE单节点预付费 hbasepre_single73、 RDS主机组ESSD主实例 rds_machineinstance_public_cn74、 云数据库专属集群主机 mysql_machine_pre75、 云数据库专属集群(托管版)主机包年包月 cddc_hostprepaid_public_cn76、 云数据专属集群存储资源包 cddc_scu_bag77、 Redis专属主机组 kvstore_machine_public_cn78、 御膳房RDS ysfrds79、 RDS-线下补款 rds_xianxia80、 关系型数据库RDS(包月) rds81、 RDS计算资源包 rds_cuudr_dp_cn82、 RDS存储资源包 rds_storageudr_dp_cn83、 数据库节省计划 rds_sp_public_cn84、 mysql_xianxia mysql_xianxia85、 云数据库RDSPostgreSQL包年包月 postgresql_pre86、 RDS只读(包月) rds_rordspre_public_cn87、 云数据库OceanBase-包年包月 oceanbase_oceanbasepre_public_cn88、 图数据库包年包月 gds_pre89、 数据传输服务DTS dtspre90、 专家服务 dbes_es_pre91、 数据库备份DBS cbs_network_bag92、 数据库备份DBS cbspre93、 数据库备份DBS cbs_storage_bag94、 DAS专业版 hdm_charge_public_cn95、 数据管理-新版(包年包月) dms_pre_public_cn96、 数据管理-企业版(包年包月) dmsenterprise97、 数据管理(包年包月) dmspre98、 云数据库MongoDB版副本集(包年包月) badds99、 云数据库MongoDB版分片集群(包年包月) badds_sharding100、 云MongoDB存储资源包 mongodb_storageudr_dp_cn101、 云MongoDB计算资源包 mongodb_computeudr_dp_cn102、 短视频SDK vodsdk103、 美颜特效SDK live_queen_public_cn104、 通用媒体服务点数包 ice_SaaSPack_dp_cn105、 作品著作权登记 swcopyright_works_public_cn106、 计算机软件著作权登记服务(纸质登记) swcopyright_swRegNormal_public_cn107、 ICP备案迁移服务 domain_ICPMigrate_public_cn108、 ICP经营许可 companyreg_icpnew_public_cn109、 EDI经营许可 companyreg_edinew_public_cn110、 ISP许可证 companyreg_ispapply_public_cn111、 SP许可证 companyreg_spapply_public_cn112、 IDC许可证 companyreg_idcapply_public_cn113、 CDN许可证 companyreg_cdnapply_public_cn114、 食品经营许可证 companyreg_foodbizlicense_public_cn115、 互联网药品信息服务资格证 companyreg_netmedicine_public_cn116、 广播电视节目制作经营许可证 companyreg_tvlisence_public_cn117、 高新技术企业认定 companyreg_hightech_public_cn118、 网络文化经营许可证 companyreg_netculturelicense_public_cn119、 ISO9001质量管理体系认证 companyreg_iso9001_public_cn120、 ISO14001环境管理体系认证 companyreg_iso14001_public_cn121、 ISO45001职业健康安全管理体系认证 companyreg_iso45001_public_cn122、 ISO27001信息安全管理体系认证 companyreg_iso27001_public_cn123、 ISO20000信息技术服务管理体系认证 companyreg_iso20000_public_cn124、 企业AAA认证咨询 companyreg_ep3acertification_public_cn125、 专精特新资质认证咨询顾问 companyreg_specializenewadviser_public_cn126、 备案管家 companyreg_icpbutler_public_cn127、 备案保镖服务 companyreg_icpbodyguard_public_cn128、 云上公司注册商品 companyreg_cloudreg129、 云上公司变更 companyreg_cloudregchange_public_cn130、 云上公司注销 companyreg_cloudregcancel_public_cn131、 公司注册商品 companyreg_localpre_public_cn132、 工商变更 companyreg_regchange_public_cn133、 工商注销 companyreg_regcancel_public_cn134、 智能记账工具 companyreg_aiaccount_public_cn135、 云服务器ECS(包月) vm136、 节省计划 savingplan_common_public_cn137、 实时数仓Hologres独享实例(包年包月) hologram_combo_public_cn138、 阿里云Elasticsearch(包月) elasticsearchpre139、 DataWorks数据安全合规服务 dide_cpltests_public_cn140、 大数据计算服务MaxCompute(包年包月) odpsplus141、 MaxCompute计算抵扣包 odps_cu_dp_cn142、 MaxCompute存储抵扣包 odps_storage_dp_cn143、 DataWorks增值版本(包年包月) dide_pre144、 DataWorks独享资源(包年包月) dide_resource_pre145、 flink全托管 sc_flinkserverless_public_cn146、 开放搜索包年包月 opensearch_pre147、 智能推荐 airecpre148、 E-MapReduce(包年包月) emrpre149、 EAS专属机器预付费 learn_EasDedicatedPrepay_public_cn150、 对象存储OSS资源包(包月) ossbag151、 NAS资源包 nas_bag152、 通用型NAS存储包 naspackage153、 存储容量单位包 scu_bag154、 媒体数据处理资源包 scu_DataProcessing_dp_cn155、 日志服务预付计划2.0 sls_plan_bag156、 日志服务资源包读写 slsbagflow157、 日志服务资源包索引 slsbagindex158、 日志服务存储包(包月) slsbag159、 表格存储OTS资源包(包月) otsbag160、 表格存储标准实例 ots_standard_public_cn161、 云备份服务HBR资源包 hbr_resourceplan_dp_cn162、 网盘与相册服务企业版 pds_edm_public_cn163、 EMAS-移动热修复设备资源包 hotfix_device_bag164、 EMAS-移动测试远程测试资源包 mqcremotebag165、 EMAS-移动测试基础测试资源包 mqcbag166、 EMAS-移动推送资源包 cpsbag167、 EMAS-HTTPDNS资源包 httpdnsbag168、 EMAS-移动监控(AppMonitor)资源包 emas_appmonitor_bag169、 阿里云RPA机器人 codestore_rparobot_pre170、 阿里云RPA编辑器 codestore_rpastudio_pre171、 能耗宝 energybrain_energy_public_cn172、 数字化低碳认证 energyexpert_certification_dp_cn173、 云智能客服在线工作台 ccs_workspace_public_cn174、 视觉AI图像通用资源包 viapi_imagecommonbag_dp_cn175、 文生图资源包 viapi_imageenhanbag_dp_cn176、 文字识别资源包 viapi_ocr1_bag177、 视频通用时长资源包 viapi_videocommonbag_dp_cn178、 视觉智能平台离线SDK viapi_offlinesdk_public_cn179、 录音文件识别资源包 nlsfilebag180、 录音文件识别(极速版) nls_rfrhsv_bag181、 录音文件识别(闲时版) nls_asrsalebag_dp_cn182、 实时语音识别资源包 nlsasrbag183、 一句话识别资源包 nlsshortasrbag184、 语音合成资源包 nlsttsbag185、 长文本语音合成资源包 nls_longtts_bag186、 说话人识别资源包 nls_SpeakerVerification_dp_cn187、 声音事件检测 nls_AudioEventDetection_dp_cn188、 性别识别 nls_GenderIdentification_dp_cn189、 语种识别 nls_LanguageIdentificati_dp_cn190、 图像搜索 imagesearch191、 云邮箱 lx_86666192、 企业邮箱教育版 alimail_20145_public_cn193、 邮件推送资源包(包月) dmbag194、 语种识别资源包 alimt_ld_bag195、 文档翻译资源包 alimt_dt_bag196、 图片翻译资源包 alimt_ct_bag197、 NLP基础服务2.0_高级版_资源包 nlp_alinlpadvanced_bag198、 NLP自学习平台模型调用资源包 nlp_bag199、 NLP基础服务2.0_基础版_资源包 nlp_alinlpbase_bag200、 机器翻译资源包 alimt_package201、 音视频翻译资源包 alimt_vt_dp_cn202、 地址标准化-基础服务-资源包 addrp_basicservicepackage_bag203、 地址标准化-业务相关服务-资源包 addrp_advancedservicepackage_bag204、 DataV数据可视化 datav205、 通用文字识别资源包 ocr_general_dp_cn206、 个人证照识别资源包 ocr_personalcard_dp_cn207、 车辆物流识别资源包 ocr_logistics_dp_cn208、 小语种识别资源包 ocr_multilanguage_dp_cn209、 企业资质识别资源包 ocr_enterprisecard_dp_cn210、 教育场景识别资源包 ocr_education_dp_cn211、 DataV按量计费 datav_num_pre212、 DataV按时长计费 datav_time_pre213、 宜搭 yida_aliyun_public_cn214、 语音专线接入服务-包年 ccc_sipvpn_pre215、 云呼叫中心语音包 cccpack_public216、 智能对话分析资源包 sca_package217、 物联网平台企业版实例 iot_instc_public_cn218、 无影云桌面 gws_ecdmonthly_public_cn219、 IDaaS安全认证 idaas_auth_public_cn220、 云身份服务 idaas_eiam_public_cn221、 云安全中心 sas222、 云防火墙 vipcloudfw223、 数据库审计 dbaudit224、 数据安全中心 sddp_pre225、 运维安全中心(堡垒机) bastionhost226、 特权访问管理中心 bastionhost_pam_public_cn227、 验证码服务包 waf_captcha_bag228、 Web应用防火墙2.0(包年包月) waf229、 WAF2.0按量资源包 waf_rsrcpack_bag230、 WAF3.0SeCU资源包 waf_secu_dp_cn231、 Web应用防火墙3.0(包年包月) waf_v3prepaid_public_cn232、 DDoS高防(新BGP) ddoscoo233、 DDoS高防(国际) ddosDip234、 DDoS原生防护 ddosbgp235、 办公安全平台 csas_VIPSASE_public_cn236、 QuickBI quickbi_deluxe_public_cn237、 QuickBI quickbi238、 云商机基础版 alicloudproc_CRM_public_cn239、 云邮箱 lx_20162240、 云支出_软件服务 Internetbank_elink_public_cn241、 网盘与相册服务 pds_01_public_cn242、 票证核验 ocr_cardverification_public_cn243、 云邮箱 lx_86666244、 票据凭证识别资源包 ocr_invoice_dp_cn245、 Logo高端定制 premiumpics_customlogo_public_cn246、 VI定制设计 premiumpics_videsign_public_cn247、 计算机软件著作权登记服务(APP软件在线登记) swcopyright_swRegOnlineNew_public_cn248、 网站备案与合规课程 companyreg_icpcompliancecourse_public_cn249、 云邮箱 alimail250、 阿里邮箱钉钉融合版 alimail_20164_public_cn
文章
存储  ·  监控  ·  NoSQL  ·  安全  ·  Cloud Native  ·  关系型数据库  ·  测试技术  ·  数据库  ·  Redis  ·  RDS
2023-03-30
在 Rainbond 上使用 Curve 云原生存储
Curve 是网易主导自研的现代化存储系统, 目前支持文件存储(CurveFS)和块存储(CurveBS)。CurveBS 的核心应用场景主要包括:虚拟机/容器的性能型、混合型、容量型云盘或持久化卷,以及物理机的远程存储盘高性能存算分离架构:基于RDMA+SPDK的高性能低时延架构,支撑MySQL、kafka等各类数据库、中间件的存算分离部署架构,提升实例交付效率和资源利用率CurveFS 的核心应用场景主要包括:AI训练(含机器学习等)场景下的高性价比存储大数据场景下的冷热数据自动化分层存储公有云上高性价比的共享文件存储:可用于AI、大数据、文件共享等业务场景混合云存储:热数据存储在本地IDC,冷数据存储在公有云使用 CurveAdm 部署 CurveFSCurveAdm 是 Curve 团队为提高系统易用性而设计的工具,其主要用于快速部署和运维 CurveBS/CurveFS 集群。主要特性:快速部署 CurveBS/CurveFS 集群容器化服务运维 CurveBS/CurveFS 集群同时管理多个集群一键升级错误精确定位安装 CurveAdmbash -c "$(curl -fsSL https://curveadm.nos-eastchina1.126.net/script/install.sh)"主机列表主机模块用来统一管理用户主机,以减少用户在各配置文件中重复填写主机 SSH 连接相关配置。我们需导入部署集群和客户端所需的机器列表,以便在之后的各类配置文件中填写部署服务的主机名。这里采用一台服务器,做单节点集群。配置免密登陆生成密钥并配置服务器免密登陆# 一直回车即可 ssh-keygen # 使用 ssh-copy-id 配置 ssh-copy-id root@172.31.98.243 # 验证免密 ssh root@172.31.98.243 # 无需输入密码登陆成功即可导入主机列表准备主机列表文件 hosts.yaml$ vim hosts.yaml global: user: root # ssh 免密登陆用户名 ssh_port: 22 # ssh 端口 private_key_file: /root/.ssh/id_rsa # 密钥路径 hosts: - host: curve hostname: 172.31.98.243导入主机列表$ curveadm hosts commit hosts.yaml查看主机列表$ curveadm hosts ls准备集群拓扑文件CurveFS 支持单机部署和高可用部署,这里我们采用单机部署验证。创建 topology.yaml 文件,只需修改 target: curve,其他都默认即可。$ vim topology.yaml kind: curvefs global: report_usage: true data_dir: ${home}/curvefs/data/${service_role}${service_host_sequence} log_dir: ${home}/curvefs/logs/${service_role}${service_host_sequence} container_image: opencurvedocker/curvefs:v2.4 variable: home: /tmp target: curve etcd_services: config: listen.ip: ${service_host} listen.port: 2380${service_host_sequence} # 23800,23801,23802 listen.client_port: 2379${service_host_sequence} # 23790,23791,23792 deploy: - host: ${target} - host: ${target} - host: ${target} mds_services: config: listen.ip: ${service_host} listen.port: 670${service_host_sequence} # 6700,6701,6702 listen.dummy_port: 770${service_host_sequence} # 7700,7701,7702 deploy: - host: ${target} - host: ${target} - host: ${target} metaserver_services: config: listen.ip: ${service_host} listen.port: 680${service_host_sequence} # 6800,6801,6802 listen.external_port: 780${service_host_sequence} # 7800,7801,7802 global.enable_external_server: true metaserver.loglevel: 0 braft.raft_sync: false deploy: - host: ${target} - host: ${target} - host: ${target} config: metaserver.loglevel: 0部署集群添加 my-cluster 集群,并指定集群拓扑文件curveadm cluster add my-cluster -f topology.yaml切换 my-cluster 集群为当前管理集群curveadm cluster checkout my-cluster开始部署集群$ curveadm deploy ...... Cluster 'my-cluster' successfully deployed ^_^.终端出现 Cluster 'my-cluster' successfully deployed ^_^. 即部署成功。查看集群运行情况$ curveadm status Get Service Status: [OK] cluster name : my-cluster cluster kind : curvefs cluster mds addr : 192.168.3.81:6700,192.168.3.81:6701,192.168.3.81:6702 cluster mds leader: 192.168.3.81:6702 / 7f5b7443c563 Id Role Host Replicas Container Id Status -- ---- ---- -------- ------------ ------ 6ae9ac1ae448 etcd curve 1/1 d3ecb4e81318 Up 17 minutes c45e2f0b9266 etcd curve 1/1 8ce9befa54b8 Up 17 minutes 6c6bde442a04 etcd curve 1/1 cbf093c6605f Up 17 minutes 9516d8f5d9ae mds curve 1/1 f338ec63c493 Up 17 minutes fe2bf5d8a072 mds curve 1/1 b423c3351256 Up 17 minutes 7f5b7443c563 mds curve 1/1 7ad99cee6b61 Up 17 minutes e6fe68d23220 metaserver curve 1/1 d4a8662d4ed2 Up 17 minutes b2b4dbabd7bf metaserver curve 1/1 65d7475e0bc4 Up 17 minutes 426ac76e28f9 metaserver curve 1/1 f413efeeb5c9 Up 17 minutes部署 RainbondRainbond 是一个云原生应用管理平台,使用简单,不需要懂容器、Kubernetes和底层复杂技术,支持管理多个Kubernetes集群,和管理企业应用全生命周期。可以通过一条命令快速安装 Rainbond 单机版。curl -o install.sh https://get.rainbond.com && bash ./install.sh执行完上述脚本后,耐心等待 3-5 分钟,可以看到如下日志输出,表示 Rainbond 已启动完成。INFO: Rainbond started successfully, Please pass http://$EIP:7070 Access Rainbond部署 MinIO由于目前 CurveFS 只支持 S3 作为后端存储,CurveBS 后端即将支持。 所以我们需要部署一个 MinIO 对象存储。通过 Rainbond 开源应用商店一键部署单机版 MinIO 或者集群版 MinIO。进入到 Rainbond 的 平台管理 -> 应用市场,在开源应用商店中搜索 minio 进行一键安装。部署完成后,通过 Rainbond 提供的域名访问 MinIO 控制台,默认用户密码 minio/minio123456。然后需要创建一个 Bucket 供 CurveFS 使用。部署 CurveFS-CSI前提:Rainbond 版本要在 v5.13+通过 Rainbond 开源应用商店一键部署,进入到 Rainbond 的 平台管理 -> 应用市场,在开源应用商店中搜索 curve-csi 进行一键安装。由于 CurveFS-CSI 没有 Rainbond 应用模型类的组件,都属于 k8s 资源类型,可在 应用视图内 -> k8s资源 下看到。安装完成后,需要修改 curvefs-csi-cluster-role-binding 和 curvefs-csi-role-binding 的 namespace 为当前团队的 namespace,如当前团队 namespace 为 dev,如下:# curvefs-csi-role-binding apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: curvefs-csi-role-binding ...... subjects: - kind: ServiceAccount name: curvefs-csi-service-account namespace: dev # changed # curvefs-csi-cluster-role-binding apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: curvefs-csi-cluster-role-binding ...... subjects: - kind: ServiceAccount name: curvefs-csi-service-account namespace: dev # changed创建 storageclass 资源,同样在 应用视图内 -> k8s资源 -> 添加:kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: curvefs-sc provisioner: csi.curvefs.com allowVolumeExpansion: false reclaimPolicy: Delete parameters: mdsAddr: "172.31.98.243:6700,172.31.98.243:6701,172.31.98.243:6702" fsType: "s3" s3Endpoint: "http://9000.grda6567.1frt0lmq.b836cf.grapps.cn" s3AccessKey: "minio" s3SecretKey: "minio123456" s3Bucket: "curve"mdsAddr:通过 curveadm status 命令获取。$ curveadm status ...... cluster mds addr : 172.31.98.243:6700,172.31.98.243:6701,172.31.98.243:6702s3Endpoint:填写 MinIO 组件的 9000 端口对外服务域名。s3AccessKey:MinIO 访问 Key,填 root 用户或生成 AccessKey。s3SecretKey:MinIO 密钥 Key,填 root 密码或生成 SecretKey。s3Bucket:MinIO 桶名称。在 Rainbond 上使用 CurveFS通过镜像创建一个 Nginx 组件,在 组件 -> 其他设置 修改组件部署类型为 有状态服务。在 Rainbond 上只有 有状态服务 可以使用自定义存储,无状态服务使用默认的共享存储。进入到 组件 -> 存储 添加存储,选择类型为 curvefs-sc,保存并重启组件。等待组件启动完成后,进入组件的 Web 终端内,测试写入数据。然后进入到 MinIO 桶内查看,数据已写入。未来规划Rainbond 社区未来会使用 Curve 云原生存储作为 Rainbond 底层的共享存储,为用户提供更好、更简单的云原生应用管理平台和云原生存储,共同推进开源社区生态以及给用户提供一体化的解决方案。
文章
存储  ·  人工智能  ·  运维  ·  Kubernetes  ·  Cloud Native  ·  大数据  ·  文件存储  ·  数据安全/隐私保护  ·  块存储  ·  容器
2023-03-29
单点登录
什么是单点登录单点登录 多系统,单一位置登录,实现多系统同时登录的一种技术三方登录 某系统使用其他系统的用户,实现本系统登录的方式,比如使用微信登录就是使用第三方登录 。跨域 跨域是指浏览器不能直接访问其他域名下的资源,包括不同协议、不同端口、不同域名等。这是由于浏览器的同源策略所如何解决跨域JSONP:通过动态创建CORS:即跨域资源共享,是一种官方标准的解决跨域问题的方案。通过在服务器端设置响应头Access-Control-Allow-Origin,允许指定的域名访问该资源。代理:通过在同一域名下设置代理服务器,将跨域请求转发到目标服务器,然后将目标服务器的响应返回给浏览器,从而实现跨域访问。WebSocket:WebSocket协议可以在浏览器和服务器之间建立长连接,从而实现跨域通信。单点登录的实现方式Cookie基于Cookie的单点登录:用户在第一次登录时,服务器在用户的浏览器中设置一个cookie,该cookie包含用户的登录信息。当用户访问其他应用程序时,这些应用程序会检查cookie中的信息,如果存在则认为用户已经登录。跨域cookie 在默认情况下,浏览器会限制跨域请求的Cookie,以保护用户的隐私和安全。但是,有时候我们需要在不同域名之间共享Cookie,这就需要解决跨域问题。下面是一些解决跨域问题的方法:Sessionsession共享Session共享是指多个应用程序之间共享同一个用户Session信息的过程。在Web应用程序中,Session是用来存储用户状态信息的一种机制,它可以在用户访问同一个应用程序的不同页面时,保持用户的状态信息不变。然而,当用户访问不同的应用程序时,每个应用程序都会创建一个新的Session,这样就导致了Session信息的不一致性。为了解决这个问题,我们可以使用Session共享技术,将Session信息存储在一个外部存储中,比如Redis、MongoDB等,从而实现多个应用程序之间的Session共享。在实现Session共享时,我们需要考虑一些问题,比如Session的序列化和反序列化、Session的过期策略、Session的访问控制等。我们可以使用一些现成的框架来实现Session共享,比如Spring Session、Apache Shiro等。这些框架提供了一些方便的API和配置选项,使得Session共享变得更加容易和灵活。如何实现session共享数据库共享:将session存储在数据库中,不同的应用服务器通过访问同一数据库来实现session共享。缓存共享:将session存储在缓存中,不同的应用服务器通过访问同一缓存来实现session共享。常用的缓存系统有Redis、Memcached等。文件共享:将session存储在共享文件系统中,不同的应用服务器通过访问同一文件系统来实现session共享。Cookie共享:将session ID存储在cookie中,不同的应用服务器通过访问同一域名下的cookie来实现session共享。session跨域和解决方案所谓 Session 跨域就是摒弃了系统(Tomcat)提供的 Session,而使用自定义的类似 Session 的机制来保存客户端数据的一种解决方案。 如:通过设置 cookie 的 domain 来实现 cookie 的跨域传递。在 cookie 中传递一个自定义 的 session_id。这个 session_id 是客户端的唯一标记。将这个标记作为 key,将客户端需要保 存的数据作为 value,在服务端进行保存(数据库保存或 NoSQL 保存)。这种机制就是 Session 的跨域解决。使用同一顶级域名下的二级域名。比如,a.example.com和b.example.com都是example.com的子域名,它们可以通过设置Cookie的domain属性为“.example.com”来共享Cookie。使用iframe或者JSONP跨域通信。这种方式可以通过在同一页面中嵌入iframe或者使用JSONP的方式来实现跨域通信,从而共享Cookie。使用CORS(跨域资源共享)。CORS是一种浏览器技术,它允许服务器在HTTP响应头中指定允许跨域访问的域名,从而实现跨域共享Cookie。Token基于Token的单点登录:使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程 是这样的: 客户端使用用户名、密码请求登录 服务端收到请求,去验证用户名、密码 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户 端返回请求的数据 使用 Token 验证的优势: 无状态、可扩展 在客户端存储的 Tokens 是无状态的,并且能够被扩展。基于这种无状态和不存储 Session 信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。 安全性 请求中发送 token 而不再是发送 cookie 能够防止 CSRF(跨站请求伪造)。即使在客户端使 用 cookie 存储 token,cookie 也仅仅是一个存储机制而不是用于认证。不将信息存储在 Session 中,让我们少了对 session 操作。Oauth2基于OAuth2.0的单点登录:OAuth2.0是一种授权框架,可以用于实现单点登录。用户在第一次登录时,服务器会将用户的授权信息存储在OAuth2.0服务器中。当用户访问其他应用程序时,这些应用程序会向OAuth2.0服务器请求授权信息,如果授权信息有效则认为用户已经登录。jwt1 JWT(JSON Web Token)是一种用于身份验证和授权的开放标准。它可以在不同的应用程序之间共享用户身份信息,从而实现单点登录(SSO)。JWT 是一种紧凑且自包含的,用于在多方传递 JSON 对象的技术。传递的数据可以使用 数字签名增加其安全行。可以使用 HMAC 加密算法或 RSA 公钥/私钥加密方式。紧凑:数据小,可以通过 URL,POST 参数,请求头发送。且数据小代表传输速度快。自包含:使用 payload 数据块记录用户必要且不隐私的数据,可以有效的减少数据库访 问次数,提高代码性能。JWT 一般用于处理用户身份验证或数据信息交换。 用户身份验证:一旦用户登录,每个后续请求都将包含 JWT,允许用户访问该令牌允许 的路由,服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并 且能够轻松地跨不同域使用。2 实现JWT单点登录的一般步骤如下:用户在应用程序A中登录,应用程序A验证用户身份成功后,生成一个JWT Token,并将该Token返回给用户。用户在访问应用程序B时,将该Token作为请求头或请求参数发送给应用程序B。应用程序B接收到请求后,解析Token,验证Token的有效性和正确性。如果Token验证通过,则应用程序B认为用户已经登录,并且可以使用Token中包含的用户信息进行授权和访问控制。3 在实现JWT单点登录时,我们需要注意以下几点:JWT Token需要使用安全的算法进行签名和加密,以防止Token被篡改或伪造。JWT Token中应该只包含最少的用户信息,比如用户ID、用户名等,而不应该包含敏感信息,比如密码等。JWT Token应该设置有效期,以防止Token被滥用。应用程序需要对Token进行管理和撤销,比如在用户退出登录时,需要将Token失效。框架实现shiro和spring security Shiro和Spring Security都是用于身份验证和授权的Java安全框架,它们的主要区别如下:架构设计:Shiro是一个轻量级的框架,它的设计目标是简单、易用、灵活,可以与任何框架和应用程序集成;而Spring Security是一个全面的安全框架,它是基于Spring框架的,因此可以很好地与Spring应用程序集成。功能特点:Shiro提供了身份验证、授权、加密、会话管理等基本安全功能,同时还支持Web应用程序和非Web应用程序;而Spring Security提供了与Spring框架集成的全面安全功能,包括基于URL的访问控制、方法级别的访问控制、LDAP集成、OpenID集成、OAuth集成等。配置方式:Shiro的配置方式比较简单,可以通过INI文件、XML文件、Java代码等方式进行配置;而Spring Security的配置方式比较复杂,需要使用XML或Java配置文件进行配置。社区支持:Spring Security是由Spring社区维护和支持的,因此拥有更多的开发者和用户,可以获得更好的技术支持和文档资料;而Shiro的社区相对较小,但是Shiro的文档和示例比较丰富,易于学习和使用。综上所述,Shiro适合小型应用程序和非Spring应用程序,它的设计简单、易用、灵活;而Spring Security适合大型应用程序和Spring应用程序,它提供了全面的安全功能和与Spring框架的深度集成。
文章
存储  ·  缓存  ·  安全  ·  NoSQL  ·  Java  ·  应用服务中间件  ·  数据库  ·  数据安全/隐私保护  ·  数据格式  ·  Spring
2023-03-14
Java最全八股文(2023最新整理)
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~Github地址:https://github.com/Tyson0314/Java-learningJava的特点Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。Java具有平台独立性和移植性。Java有一句口号:Write once, run anywhere,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。Java具有稳健性。Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用try/catch/finally语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务。Java是如何实现跨平台的?Java是通过JVM(Java虚拟机)实现跨平台的。JVM可以理解成一个软件,不同的平台有不同的版本。我们编写的Java代码,编译后会生成.class 文件(字节码文件)。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码,通过JVM翻译成机器码之后才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。因此,运行Java程序必须有JVM的支持,因为编译的结果不是机器码,必须要经过JVM的翻译才能执行。Java 与 C++ 的区别Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 兼容 C ,不但支持面向对象也支持面向过程。Java 通过虚拟机从而实现跨平台特性, C++ 依赖于特定的平台。Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。Java 支持自动垃圾回收,而 C++ 需要手动回收。Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。JDK/JRE/JVM三者的关系JVM英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。JRE英文名称(Java Runtime Environment),就是Java 运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分,JVM 和 Java 核心类库。JRE是Java的运行环境,并不是一个开发环境,所以没有包含任何开发工具,如编译器和调试器等。如果你只是想运行Java程序,而不是开发Java程序的话,那么你只需要安装JRE即可。JDK英文名称(Java Development Kit),就是 Java 开发工具包学过Java的同学,都应该安装过JDK。当我们安装完JDK之后,目录结构是这样的可以看到,JDK目录下有个JRE,也就是JDK中已经集成了 JRE,不用单独安装JRE。另外,JDK中还有一些好用的工具,如jinfo,jps,jstack等。最后,总结一下JDK/JRE/JVM,他们三者的关系JRE = JVM + Java 核心类库JDK = JRE + Java工具 + 编译器 + 调试器Java程序是编译执行还是解释执行?先看看什么是编译型语言和解释型语言。编译型语言在程序运行之前,通过编译器将源程序编译成机器码可运行的二进制,以后执行这个程序时,就不用再进行编译了。优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高,可以脱离语言环境独立运行。缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。总结:执行速度快、效率高;依靠编译器、跨平台性差些。代表语言:C、C++、Pascal、Object-C以及Swift。解释型语言定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(如虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。总结:解释型语言执行速度慢、效率低;依靠解释器、跨平台性好。代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。对于Java这种语言,它的源代码会先通过javac编译成字节码,再通过jvm将字节码转换成机器码执行,即解释运行 和编译运行配合使用,所以可以称为混合型或者半编译型。面向对象和面向过程的区别?面向对象和面向过程是一种软件开发思想。面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。面向对象是把构成问题事务分解成各个对象,分别设计这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象是用类实现各个功能模块。以五子棋为例,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:黑白双方棋盘系统,负责绘制画面规则系统,负责判定诸如犯规、输赢等。黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化,同时利用规则系统来对棋局进行判定。面向对象有哪些特性?面向对象四大特性:封装,继承,多态,抽象1、封装就是将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。2、继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。3、多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定的代码。实现多态的三要素:继承、重写、父类引用指向子类对象。静态多态性:通过重载实现,相同的方法有不同的參数列表,可以根据参数的不同,做出不同的处理。动态多态性:在子类中重写父类的方法。运行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。4、抽象。把客观事物用代码抽象出来。面向对象编程的六大原则?对象单一职责:我们设计创建的对象,必须职责明确,比如商品类,里面相关的属性和方法都必须跟商品相关,不能出现订单等不相关的内容。这里的类可以是模块、类库、程序集,而不单单指类。里式替换原则:子类能够完全替代父类,反之则不行。通常用于实现接口时运用。因为子类能够完全替代基(父)类,那么这样父类就拥有很多子类,在后续的程序扩展中就很容易进行扩展,程序完全不需要进行修改即可进行扩展。比如IA的实现为A,因为项目需求变更,现在需要新的实现,直接在容器注入处更换接口即可.迪米特法则,也叫最小原则,或者说最小耦合。通常在设计程序或开发程序的时候,尽量要高内聚,低耦合。当两个类进行交互的时候,会产生依赖。而迪米特法则就是建议这种依赖越少越好。就像构造函数注入父类对象时一样,当需要依赖某个对象时,并不在意其内部是怎么实现的,而是在容器中注入相应的实现,既符合里式替换原则,又起到了解耦的作用。开闭原则:开放扩展,封闭修改。当项目需求发生变更时,要尽可能的不去对原有的代码进行修改,而在原有的基础上进行扩展。依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。接口隔离原则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象单一职责的情况下,尽量减少接口的内容。简洁版:单一职责:对象设计要求独立,不能设计万能对象。开闭原则:对象修改最小化。里式替换:程序扩展中抽象被具体可以替换(接口、父类、可以被实现类对象、子类替换对象)迪米特:高内聚,低耦合。尽量不要依赖细节。依赖倒置:面向抽象编程。也就是参数传递,或者返回值,可以使用父类类型或者接口类型。从广义上讲:基于接口编程,提前设计好接口框架。接口隔离:接口设计大小要适中。过大导致污染,过小,导致调用麻烦。数组到底是不是对象?先说说对象的概念。对象是根据某个类创建出来的一个实例,表示某类事物中一个具体的个体。对象具有各种属性,并且具有一些特定的行为。站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性。所以,对象是用来封装数据的。java中的数组具有java中其他对象的一些基本特点。比如封装了一些数据,可以访问属性,也可以调用方法。因此,可以说,数组是对象。也可以通过代码验证数组是对象的事实。比如以下的代码,输出结果为java.lang.Object。Class clz = int[].class; System.out.println(clz.getSuperclass().getName());由此,可以看出,数组类的父类就是Object类,那么可以推断出数组就是对象。Java的基本数据类型有哪些?byte,8bitchar,16bitshort,16bitint,32bitfloat,32bitlong,64bitdouble,64bitboolean,只有两个值:true、false,可以使⽤用 1 bit 来存储简单类型booleanbytecharshortIntlongfloatdouble二进制位数18161632643264包装类BooleanByteCharacterShortIntegerLongFloatDouble在Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 还要看虚拟机实现是否按照规范来,因此boolean占用1个字节或者4个字节都是有可能的。为什么不能用浮点型表示金额?由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。建议使用BigDecimal或者Long来表示金额。什么是值传递和引用传递?值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。java中不存在引用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象的这种情况。了解Java的包装类型吗?为什么需要包装类?Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。为了让基本类型也具有对象的特征,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。自动装箱和拆箱Java中基础数据类型与它们对应的包装类见下表:原始类型包装类型booleanBooleanbyteBytecharCharacterfloatFloatintIntegerlongLongshortShortdoubleDouble装箱:将基础类型转化为包装类型。拆箱:将包装类型转化为基础类型。当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:赋值操作(装箱或拆箱)进行加减乘除混合运算 (拆箱)进行>,<,==比较运算(拆箱)调用equals进行比较(装箱)ArrayList、HashMap等集合类添加基础类型数据时(装箱)示例代码:Integer x = 1; // 装箱 调⽤ Integer.valueOf(1) int y = x; // 拆箱 调⽤了 X.intValue()下面看一道常见的面试题:Integer a = 100; Integer b = 100; System.out.println(a == b); Integer c = 200; Integer d = 200; System.out.println(c == d);输出:true false为什么第三个输出是false?看看 Integer 类的源码就知道啦。public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }Integer c = 200; 会调用 调⽤Integer.valueOf(200)。而从Integer的valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做一个cache。private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; } ... }这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以==比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==比较的结果是不相等的。String 为什么不可变?先看看什么是不可变的对象。如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。接着来看Java8 String类的源码:public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 }从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。所以,String是不可变的。那为什么String要设计成不可变的?主要有以下几点原因:线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。支持hash映射和缓存。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。出于安全考虑。网络地址URL、文件路径path、密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。字符串常量池优化。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。既然我们的String是不可变的,它内部还有很多substring, replace, replaceAll这些操作的方法。这些方法好像会改变String对象?怎么解释呢?其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。为何JDK9要将String的底层实现由char[]改成byte[]?主要是为了节约String占用的内存。在大部分Java程序的堆内存中,String占用的空间最大,并且绝大多数String只有Latin-1字符,这些Latin-1字符只需要1个字节就够了。而在JDK9之前,JVM因为String使用char数组存储,每个char占2个字节,所以即使字符串只需要1字节,它也要按照2字节进行分配,浪费了一半的内存空间。到了JDK9之后,对于每个字符串,会先判断它是不是只有Latin-1字符,如果是,就按照1字节的规格进行分配内存,如果不是,就按照2字节的规格进行分配,这样便提高了内存使用率,同时GC次数也会减少,提升效率。不过Latin-1编码集支持的字符有限,比如不支持中文字符,因此对于中文字符串,用的是UTF16编码(两个字节),所以用byte[]和char[]实现没什么区别。String, StringBuffer 和 StringBuilder区别1. 可变性String 不可变StringBuffer 和 StringBuilder 可变2. 线程安全String 不可变,因此是线程安全的StringBuilder 不是线程安全的StringBuffer 是线程安全的,内部使用 synchronized 进行同步什么是StringJoiner?StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。StringJoiner 有两个构造方法,第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可(前缀和后缀默认为空字符串)。StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) StringJoiner(CharSequence delimiter)有些字符串拼接场景,使用 StringBuffer 或 StringBuilder 则显得比较繁琐。比如下面的例子:List<Integer> values = Arrays.asList(1, 3, 5); StringBuilder sb = new StringBuilder("("); for (int i = 0; i < values.size(); i++) { sb.append(values.get(i)); if (i != values.size() -1) { sb.append(","); } } sb.append(")");而通过StringJoiner来实现拼接List的各个元素,代码看起来更加简洁。List<Integer> values = Arrays.asList(1, 3, 5); StringJoiner sj = new StringJoiner(",", "(", ")"); for (Integer value : values) { sj.add(value.toString()); }另外,像平时经常使用的Collectors.joining(","),底层就是通过StringJoiner实现的。源码如下:public static Collector<CharSequence, ?, String> joining( CharSequence delimiter,CharSequence prefix,CharSequence suffix) { return new CollectorImpl<>( () -> new StringJoiner(delimiter, prefix, suffix), StringJoiner::add, StringJoiner::merge, StringJoiner::toString, CH_NOID); }String 类的常用方法有哪些?indexOf():返回指定字符的索引。charAt():返回指定索引处的字符。replace():字符串替换。trim():去除字符串两端空白。split():分割字符串,返回一个分割后的字符串数组。getBytes():返回字符串的 byte 类型数组。length():返回字符串长度。toLowerCase():将字符串转成小写字母。toUpperCase():将字符串转成大写字符。substring():截取字符串。equals():字符串比较。new String("dabin")会创建几个对象?使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 "dabin" 这个字符串对象)。"dabin" 属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个 "dabin" 字符串字面量;使用 new 的方式会在堆中创建一个字符串对象。什么是字符串常量池?字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。String最大长度是多少?String类提供了一个length方法,返回值为int类型,而int的取值上限为2^31 -1。所以理论上String的最大长度为2^31 -1。达到这个长度的话需要多大的内存吗?String内部是使用一个char数组来维护字符序列的,一个char占用两个字节。如果说String最大长度是2^31 -1的话,那么最大的字符串占用内存空间约等于4GB。也就是说,我们需要有大于4GB的JVM运行内存才行。那String一般都存储在JVM的哪块区域呢?字符串在JVM中的存储分两种情况,一种是String对象,存储在JVM的堆栈中。一种是字符串常量,存储在常量池里面。什么情况下字符串会存储在常量池呢?当通过字面量进行字符串声明时,比如String s = "程序新大彬";,这个字符串在编译之后会以常量的形式进入到常量池。那常量池中的字符串最大长度是2^31-1吗?不是的,常量池对String的长度是有另外限制的。。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }length在这里就是代表字符串的长度,length的类型是u2,u2是无符号的16位整数,也就是说最大长度可以做到2^16-1 即 65535。不过javac编译器做了限制,需要length < 65535。所以字符串常量在常量池中的最大长度是65535 - 1 = 65534。最后总结一下:String在不同的状态下,具有不同的长度限制。字符串常量长度不能超过65534堆内字符串的长度不超过2^31-1Object常用方法有哪些?Java面试经常会出现的一道题目,Object的常用方法。下面给大家整理一下。Object常用方法有:toString()、equals()、hashCode()、clone()等。toString默认输出对象地址。public class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public static void main(String[] args) { System.out.println(new Person(18, "程序员大彬").toString()); } //output //me.tyson.java.core.Person@4554617c }可以重写toString方法,按照重写逻辑输出对象值。public class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return name + ":" + age; } public static void main(String[] args) { System.out.println(new Person(18, "程序员大彬").toString()); } //output //程序员大彬:18 }equals默认比较两个引用变量是否指向同一个对象(内存地址)。public class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public static void main(String[] args) { String name = "程序员大彬"; Person p1 = new Person(18, name); Person p2 = new Person(18, name); System.out.println(p1.equals(p2)); } //output //false }可以重写equals方法,按照age和name是否相等来判断:public class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } @Override public boolean equals(Object o) { if (o instanceof Person) { Person p = (Person) o; return age == p.age && name.equals(p.name); } return false; } public static void main(String[] args) { String name = "程序员大彬"; Person p1 = new Person(18, name); Person p2 = new Person(18, name); System.out.println(p1.equals(p2)); } //output //true }hashCode将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。public class Cat { public static void main(String[] args) { System.out.println(new Cat().hashCode()); } //out //1349277854 }cloneJava赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的。protected native Object clone() throws CloneNotSupportedException;所以实体类使用克隆的前提是:实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。覆盖clone()方法,可见性提升为public。public class Cat implements Cloneable { private String name; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { Cat c = new Cat(); c.name = "程序员大彬"; Cat cloneCat = (Cat) c.clone(); c.name = "大彬"; System.out.println(cloneCat.name); } //output //程序员大彬 }getClass返回此 Object 的运行时类,常用于java反射机制。public class Person { private String name; public Person(String name) { this.name = name; } public static void main(String[] args) { Person p = new Person("程序员大彬"); Class clz = p.getClass(); System.out.println(clz); //获取类名 System.out.println(clz.getName()); } /** * class com.tyson.basic.Person * com.tyson.basic.Person */ }wait当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。等待其他线程调用此对象的notify()/notifyAll()唤醒或者等待超时时间wait(long timeout)自动唤醒。线程需要获取obj对象锁之后才能调用 obj.wait()。notifyobj.notify()唤醒在此对象上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象上等待的所有线程。讲讲深拷贝和浅拷贝?浅拷贝:拷⻉对象和原始对象的引⽤类型引用同⼀个对象。以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。public class Cat implements Cloneable { private String name; private Person owner; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { Cat c = new Cat(); Person p = new Person(18, "程序员大彬"); c.owner = p; Cat cloneCat = (Cat) c.clone(); p.setName("大彬"); System.out.println(cloneCat.owner.getName()); } //output //大彬 }深拷贝:拷贝对象和原始对象的引用类型引用不同的对象。以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。public class Cat implements Cloneable { private String name; private Person owner; @Override protected Object clone() throws CloneNotSupportedException { Cat c = null; c = (Cat) super.clone(); c.owner = (Person) owner.clone();//拷贝Person对象 return c; } public static void main(String[] args) throws CloneNotSupportedException { Cat c = new Cat(); Person p = new Person(18, "程序员大彬"); c.owner = p; Cat cloneCat = (Cat) c.clone(); p.setName("大彬"); System.out.println(cloneCat.owner.getName()); } //output //程序员大彬 }两个对象的hashCode()相同,则 equals()是否也一定为 true?equals与hashcode的关系:如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同;如果两个对象的hashCode相同,它们并不一定相同。hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。为什么重写 equals 时一定要重写 hashCode?之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。Java创建对象有几种方式?Java创建对象有以下几种方式:用new语句创建对象。使用反射,使用Class.newInstance()创建对象。调用对象的clone()方法。运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。说说类实例化的顺序Java中类实例化顺序:静态属性,静态代码块。普通属性,普通代码块。构造方法。public class LifeCycle { // 静态属性 private static String staticField = getStaticField(); // 静态代码块 static { System.out.println(staticField); System.out.println("静态代码块初始化"); } // 普通属性 private String field = getField(); // 普通代码块 { System.out.println(field); System.out.println("普通代码块初始化"); } // 构造方法 public LifeCycle() { System.out.println("构造方法初始化"); } // 静态方法 public static String getStaticField() { String statiFiled = "静态属性初始化"; return statiFiled; } // 普通方法 public String getField() { String filed = "普通属性初始化"; return filed; } public static void main(String[] argc) { new LifeCycle(); } /** * 静态属性初始化 * 静态代码块初始化 * 普通属性初始化 * 普通代码块初始化 * 构造方法初始化 */ }equals和==有什么区别?对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法;对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。equals()默认比较地址值,重写的话按照重写逻辑去比较。常见的关键字有哪些?staticstatic可以用来修饰类的成员方法、类的成员变量。static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。以下例子,age为非静态变量,则p1打印结果是:Name:zhangsan, Age:10;若age使用static修饰,则p1打印结果是:Name:zhangsan, Age:12,因为static变量在内存只有一个副本。public class Person { String name; int age; public String toString() { return "Name:" + name + ", Age:" + age; } public static void main(String[] args) { Person p1 = new Person(); p1.name = "zhangsan"; p1.age = 10; Person p2 = new Person(); p2.name = "lisi"; p2.age = 12; System.out.println(p1); System.out.println(p2); } /**Output * Name:zhangsan, Age:10 * Name:lisi, Age:12 *///~ }static方法一般称作静态方法。静态方法不依赖于任何对象就可以进行访问,通过类名即可调用静态方法。public class Utils { public static void print(String s) { System.out.println("hello world: " + s); } public static void main(String[] args) { Utils.print("程序员大彬"); } }静态代码块只会在类加载的时候执行一次。以下例子,startDate和endDate在类加载的时候进行赋值。class Person { private Date birthDate; private static Date startDate, endDate; static{ startDate = Date.valueOf("2008"); endDate = Date.valueOf("2021"); } public Person(Date birthDate) { this.birthDate = birthDate; } }静态内部类在静态方法里,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。public class OuterClass { class InnerClass { } static class StaticInnerClass { } public static void main(String[] args) { // 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例 // 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例 // InnerClass innerClass = new InnerClass(); OuterClass outerClass = new OuterClass(); InnerClass innerClass = outerClass.new InnerClass(); StaticInnerClass staticInnerClass = new StaticInnerClass(); outerClass.test(); } public void nonStaticMethod() { InnerClass innerClass = new InnerClass(); System.out.println("nonStaticMethod..."); } }final基本数据类型用final修饰,则不能修改,是常量;对象引用用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。final修饰的方法不能被子类重写final修饰的类不能被继承。thisthis.属性名称指访问类中的成员变量,可以用来区分成员变量和局部变量。如下代码所示,this.name访问类Person当前实例的变量。/** * @description: * @author: 程序员大彬 * @time: 2021-08-17 00:29 */ public class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } }this.方法名称用来访问本类的方法。以下代码中,this.born()调用类 Person 的当前实例的方法。/** * @description: * @author: 程序员大彬 * @time: 2021-08-17 00:29 */ public class Person { String name; int age; public Person(String name, int age) { this.born(); this.name = name; this.age = age; } void born() { } }supersuper 关键字用于在子类中访问父类的变量和方法。class A { protected String name = "大彬"; public void getName() { System.out.println("父类:" + name); } } public class B extends A { @Override public void getName() { System.out.println(super.name); super.getName(); } public static void main(String[] args) { B b = new B(); b.getName(); } /** * 大彬 * 父类:大彬 */ }在子类B中,我们重写了父类的getName()方法,如果在重写的getName()方法中我们要调用父类的相同方法,必须要通过super关键字显式指出。final, finally, finalize 的区别final 用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行。finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用。final关键字的作用?final 修饰的类不能被继承。final 修饰的方法不能被重写。final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。方法重载和重写的区别?同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。重载是面向对象的一个基本特性。public class OverrideTest { void setPerson() { } void setPerson(String name) { //set name } void setPerson(String name, int age) { //set name and age } }方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。方法重写时, 方法名与形参列表必须一致。如下代码,Person为父类,Student为子类,在Student中重写了dailyTask方法。public class Person { private String name; public void dailyTask() { System.out.println("work eat sleep"); } } public class Student extends Person { @Override public void dailyTask() { System.out.println("study eat sleep"); } }接口与抽象类区别?1、语法层面上的区别抽象类可以有方法实现,而接口的方法中只能是抽象方法(Java 8 之后接口方法可以有默认实现);抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型;接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法(Java 8之后接口可以有静态方法);一个类只能继承一个抽象类,而一个类却可以实现多个接口。2、设计层面上的区别抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对类行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是具备不具备的关系,比如鸟是否能飞。继承抽象类的是具有相似特点的类,而实现接口的却可以不同的类。门和警报的例子:class AlarmDoor extends Door implements Alarm { //code } class BMWCar extends Car implements Alarm { //code }常见的Exception有哪些?常见的RuntimeException:ClassCastException //类型转换异常IndexOutOfBoundsException //数组越界异常NullPointerException //空指针ArrayStoreException //数组存储异常NumberFormatException //数字格式化异常ArithmeticException //数学运算异常checked Exception:NoSuchFieldException //反射异常,没有对应的字段ClassNotFoundException //类没有找到异常IllegalAccessException //安全权限异常,可能是反射时调用了private方法Error和Exception的区别?Error:JVM 无法解决的严重问题,如栈溢出StackOverflowError、内存溢出OOM等。程序无法处理的错误。Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。运行时异常和非运行时异常(checked)的区别?unchecked exception包括RuntimeException和Error类,其他所有异常称为检查(checked)异常。RuntimeException由程序错误导致,应该修正程序避免这类异常发生。checked Exception由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或者throws。throw和throws的区别?throw:用于抛出一个具体的异常对象。throws:用在方法签名中,用于声明该方法可能抛出的异常。子类方法抛出的异常范围更加小,或者根本不抛异常。通过故事讲清楚NIO下面通过一个例子来讲解下。假设某银行只有10个职员。该银行的业务流程分为以下4个步骤:1) 顾客填申请表(5分钟);2) 职员审核(1分钟);3) 职员叫保安去金库取钱(3分钟);4) 职员打印票据,并将钱和票据返回给顾客(1分钟)。下面我们看看银行不同的工作方式对其工作效率到底有何影响。首先是BIO方式。每来一个顾客,马上由一位职员来接待处理,并且这个职员需要负责以上4个完整流程。当超过10个顾客时,剩余的顾客需要排队等候。一个职员处理一个顾客需要10分钟(5+1+3+1)时间。一个小时(60分钟)能处理6个顾客,一共10个职员,那就是只能处理60个顾客。可以看到银行职员的工作状态并不饱和,比如在第1步,其实是处于等待中。这种工作其实就是BIO,每次来一个请求(顾客),就分配到线程池中由一个线程(职员)处理,如果超出了线程池的最大上限(10个),就扔到队列等待 。那么如何提高银行的吞吐量呢?思路就是:分而治之,将任务拆分开来,由专门的人负责专门的任务。具体来讲,银行专门指派一名职员A,A的工作就是每当有顾客到银行,他就递上表格让顾客填写。每当有顾客填好表后,A就将其随机指派给剩余的9名职员完成后续步骤。这种方式下,假设顾客非常多,职员A的工作处于饱和中,他不断的将填好表的顾客带到柜台处理。柜台一个职员5分钟能处理完一个顾客,一个小时9名职员能处理:9*(60/5)=108。可见工作方式的转变能带来效率的极大提升。这种工作方式其实就NIO的思路。下图是非常经典的NIO说明图,mainReactor线程负责监听server socket,接收新连接,并将建立的socket分派给subReactorsubReactor可以是一个线程,也可以是线程池,负责多路分离已连接的socket,读写网络数据。这里的读写网络数据可类比顾客填表这一耗时动作,对具体的业务处理功能,其扔给worker线程池完成可以看到典型NIO有三类线程,分别是mainReactor线程、subReactor线程、work线程。不同的线程干专业的事情,最终每个线程都没空着,系统的吞吐量自然就上去了。那这个流程还有没有什么可以提高的地方呢?可以看到,在这个业务流程里边第3个步骤,职员叫保安去金库取钱(3分钟)。这3分钟柜台职员是在等待中度过的,可以把这3分钟利用起来。还是分而治之的思路,指派1个职员B来专门负责第3步骤。每当柜台员工完成第2步时,就通知职员B来负责与保安沟通取钱。这时候柜台员工可以继续处理下一个顾客。当职员B拿到钱之后,通知顾客钱已经到柜台了,让顾客重新排队处理,当柜台职员再次服务该顾客时,发现该顾客前3步已经完成,直接执行第4步即可。在当今web服务中,经常需要通过RPC或者Http等方式调用第三方服务,这里对应的就是第3步,如果这步耗时较长,通过异步方式将能极大降低资源使用率。NIO+异步的方式能让少量的线程做大量的事情。这适用于很多应用场景,比如代理服务、api服务、长连接服务等等。这些应用如果用同步方式将耗费大量机器资源。不过虽然NIO+异步能提高系统吞吐量,但其并不能让一个请求的等待时间下降,相反可能会增加等待时间。最后,NIO基本思想总结起来就是:分而治之,将任务拆分开来,由专门的人负责专门的任务BIO/NIO/AIO区别的区别?同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行。同步非阻塞IO: 客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类似Future模式。守护线程是什么?守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。Java支持多继承吗?java中,类不支持多继承。接口才支持多继承。接口的作用是拓展对象功能。当一个子接口继承了多个父接口时,说明子接口拓展了多个功能。当一个类实现该接口时,就拓展了多个的功能。Java不支持多继承的原因:出于安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个。Java提供了接口和内部类以达到实现多继承功能,弥补单继承的缺陷。如何实现对象克隆?实现Cloneable接口,重写 clone() 方法。这种方式是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。如果对象的属性的Class也实现 Cloneable 接口,那么在克隆对象时也会克隆属性,即深拷贝。结合序列化,深拷贝。通过org.apache.commons中的工具类BeanUtils和PropertyUtils进行对象复制。同步和异步的区别?同步:发出一个调用时,在没有得到结果之前,该调用就不返回。异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。阻塞和非阻塞的区别?阻塞和非阻塞关注的是线程的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。举个例子,理解下同步、阻塞、异步、非阻塞的区别:同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。Java8的新特性有哪些?Lambda 表达式:Lambda允许把函数作为一个方法的参数Stream API :新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中默认方法:默认方法就是一个在接口里面有了一个实现的方法。Optional 类 :Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。Date Time API :加强对日期与时间的处理。Java8 新特性总结序列化和反序列化序列化:把对象转换为字节序列的过程称为对象的序列化.反序列化:把字节序列恢复为对象的过程称为对象的反序列化.什么时候需要用到序列化和反序列化呢?当我们只在本地 JVM 里运行下 Java 实例,这个时候是不需要什么序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘,数据库中时,当我们需要与浏览器进行交互时,当我们需要实现 RPC 时,这个时候就需要序列化和反序列化了.前两个需要用到序列化和反序列化的场景,是不是让我们有一个很大的疑问? 我们在与浏览器交互时,还有将内存中的对象持久化到数据库中时,好像都没有去进行序列化和反序列化,因为我们都没有实现 Serializable 接口,但一直正常运行.下面先给出结论:只要我们对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化.理由:服务器与浏览器交互时真的没有用到 Serializable 接口吗? JSON 格式实际上就是将一个对象转化为字符串,所以服务器与浏览器交互时的数据格式其实是字符串,我们来看来 String 类型的源码:public final class String implements java.io.Serializable,Comparable<String>,CharSequence { /\*\* The value is used for character storage. \*/ private final char value\[\]; /\*\* Cache the hash code for the string \*/ private int hash; // Default to 0 /\*\* use serialVersionUID from JDK 1.0.2 for interoperability \*/ private static final long serialVersionUID = -6849794470754667710L; ...... }String 类型实现了 Serializable 接口,并显示指定 serialVersionUID 的值.然后我们再来看对象持久化到数据库中时的情况,Mybatis 数据库映射文件里的 insert 代码:<insert id="insertUser" parameterType="org.tyshawn.bean.User"> INSERT INTO t\_user(name,age) VALUES (#{name},#{age}) </insert>实际上我们并不是将整个对象持久化到数据库中,而是将对象中的属性持久化到数据库中,而这些属性(如Date/String)都实现了 Serializable 接口。实现序列化和反序列化为什么要实现 Serializable 接口?在 Java 中实现了 Serializable 接口后, JVM 在类加载的时候就会发现我们实现了这个接口,然后在初始化实例对象的时候就会在底层帮我们实现序列化和反序列化。如果被写对象类型不是String、数组、Enum,并且没有实现Serializable接口,那么在进行序列化的时候,将抛出NotSerializableException。源码如下:// remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } }实现 Serializable 接口之后,为什么还要显示指定 serialVersionUID 的值?如果不显示指定 serialVersionUID,JVM 在序列化时会根据属性自动生成一个 serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输. 在反序列化时,JVM 会再根据属性自动生成一个新版 serialVersionUID,然后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比较,如果相同则反序列化成功,否则报错.如果显示指定了 serialVersionUID,JVM 在序列化和反序列化时仍然都会生成一个 serialVersionUID,但值为我们显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 就一致了.如果我们的类写完后不再修改,那么不指定serialVersionUID,不会有问题,但这在实际开发中是不可能的,我们的类会不断迭代,一旦类被修改了,那旧对象反序列化就会报错。 所以在实际开发中,我们都会显示指定一个 serialVersionUID。static 属性为什么不会被序列化?因为序列化是针对对象而言的,而 static 属性优先于对象存在,随着类的加载而加载,所以不会被序列化.看到这个结论,是不是有人会问,serialVersionUID 也被 static 修饰,为什么 serialVersionUID 会被序列化? 其实 serialVersionUID 属性并没有被序列化,JVM 在序列化对象时会自动生成一个 serialVersionUID,然后将我们显示指定的 serialVersionUID 属性值赋给自动生成的 serialVersionUID.transient关键字的作用?Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。也就是说被transient修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。什么是反射?动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。在运行状态中,对于任意一个类,能够知道这个类的所有属性和方法。对于任意一个对象,能够调用它的任意一个方法和属性。反射有哪些应用场景呢?JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序Eclispe、IDEA等开发工具利用反射动态解析对象的类型与结构,动态提示对象的属性和方法Web服务器中利用反射调用了Sevlet的service方法JDK动态代理底层依赖反射实现讲讲什么是泛型?Java泛型是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的时候使⽤类型参数。声明的类型参数在使⽤时⽤具体的类型来替换。泛型最⼤的好处是可以提⾼代码的复⽤性。以List接口为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。如何停止一个正在运行的线程?有几种方式。1、使用线程的stop方法。使用stop()方法可以强制终止线程。不过stop是一个被废弃掉的方法,不推荐使用。使用Stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁所有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步的处理,因此可能会造成数据不一致的问题。2、使用interrupt方法中断线程,该方法只是告诉线程要终止,但最终何时终止取决于计算机。调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。接着调用 Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果isInterrupted返回true的话,会抛一个中断异常,然后通过try-catch捕获。3、设置标志位设置标志位,当标识位为某个值时,使线程正常退出。设置标志位是用到了共享变量的方式,为了保证共享变量在内存中的可见性,可以使用volatile修饰它,这样的话,变量取值始终会从主存中获取最新值。但是这种volatile标记共享变量的方式,在线程发生阻塞时是无法完成响应的。比如调用Thread.sleep() 方法之后,线程处于不可运行状态,即便是主线程修改了共享变量的值,该线程此时根本无法检查循环标志,所以也就无法实现线程中断。因此,interrupt() 加上手动抛异常的方式是目前中断一个正在运行的线程最为正确的方式了。什么是跨域?简单来讲,跨域是指从一个域名的网页去请求另一个域名的资源。由于有同源策略的关系,一般是不允许这么直接访问的。但是,很多场景经常会有跨域访问的需求,比如,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域问题。那什么是同源策略呢?所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。同源策略限制以下几种行为:1. Cookie、LocalStorage 和 IndexDB 无法读取 2. DOM 和 Js对象无法获得 3. AJAX 请求不能发送为什么要有同源策略?举个例子,假如你刚刚在网银输入账号密码,查看了自己的余额,然后再去访问其他带颜色的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。因此,从安全的角度来讲,同源策略是有利于保护网站信息的。跨域问题怎么解决呢?嗯,有以下几种方法:CORS,跨域资源共享CORS(Cross-origin resource sharing),跨域资源共享。CORS 其实是浏览器制定的一个规范,浏览器会自动进行 CORS 通信,它的实现主要在服务端,通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器 上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。@CrossOrigin注解如果项目使用的是Springboot,可以在Controller类上添加一个 @CrossOrigin(origins ="*") 注解就可以实现对当前controller 的跨域访问了,当然这个标签也可以加到方法上,或者直接加到入口类上对所有接口进行跨域处理。注意SpringMVC的版本要在4.2或以上版本才支持@CrossOrigin。nginx反向代理接口跨域nginx反向代理跨域原理如下: 首先同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。nginx反向代理接口跨域实现思路如下:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。// proxy服务器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; add_header Access-Control-Allow-Origin http://www.domain1.com; } }这样我们的前端代理只要访问 http:www.domain1.com:81/*就可以了。通过jsonp跨域通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,这是浏览器允许的操作,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。设计接口要注意什么?接口参数校验。接口必须校验参数,比如入参是否允许为空,入参长度是否符合预期。设计接口时,充分考虑接口的可扩展性。思考接口是否可以复用,怎样保持接口的可扩展性。串行调用考虑改并行调用。比如设计一个商城首页接口,需要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比较大了。这种场景是可以改为并行调用的,降低接口耗时。接口是否需要防重处理。涉及到数据库修改的,要考虑防重处理,可以使用数据库防重表,以唯一流水号作为唯一索引。日志打印全面,入参出参,接口耗时,记录好日志,方便甩锅。修改旧接口时,注意兼容性设计。异常处理得当。使用finally关闭流资源、使用log打印而不是e.printStackTrace()、不要吞异常等等是否需要考虑限流。限流为了保护系统,防止流量洪峰超过系统的承载能力。过滤器和拦截器有什么区别?1、实现原理不同。过滤器和拦截器底层实现不同。过滤器是基于函数回调的,拦截器是基于Java的反射机制(动态代理)实现的。一般自定义的过滤器中都会实现一个doFilter()方法,这个方法有一个FilterChain参数,而实际上它是一个回调接口。2、使用范围不同。过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。而拦截器是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。拦截器不仅能应用在web程序中,也可以用于Application、Swing等程序中。3、使用的场景不同。因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:日志记录、权限判断等业务。而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、响应数据压缩等功能。4、触发时机不同。过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。5、拦截的请求范围不同。请求的执行顺序是:请求进入容器 -> 进入过滤器 -> 进入 Servlet -> 进入拦截器 -> 执行控制器。可以看到过滤器和拦截器的执行时机也是不同的,过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法。参考链接:https://segmentfault.com/a/1190000022833940对接第三方接口要考虑什么?嗯,需要考虑以下几点:确认接口对接的网络协议,是https/http或者自定义的私有协议等。约定好数据传参、响应格式(如application/json),弱类型对接强类型语言时要特别注意接口安全方面,要确定身份校验方式,使用token、证书校验等确认是否需要接口调用失败后的重试机制,保证数据传输的最终一致性。日志记录要全面。接口出入参数,以及解析之后的参数值,都要用日志记录下来,方便定位问题(甩锅)。参考:https://blog.csdn.net/gzt19881123/article/details/108791034后端接口性能优化有哪些方法?有以下这些方法:1、优化索引。给where条件的关键字段,或者order by后面的排序字段,加索引。2、优化sql语句。比如避免使用select *、批量操作、避免深分页、提升group by的效率等3、避免大事务。使用@Transactional注解这种声明式事务的方式提供事务功能,容易造成大事务,引发其他的问题。应该避免在事务中一次性处理太多数据,将一些跟事务无关的逻辑放到事务外面执行。4、异步处理。剥离主逻辑和副逻辑,副逻辑可以异步执行,异步写库。比如用户购买的商品发货了,需要发短信通知,短信通知是副流程,可以异步执行,以免影响主流程的执行。5、降低锁粒度。在并发场景下,多个线程同时修改数据,造成数据不一致的情况。这种情况下,一般会加锁解决。但如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。6、加缓存。如果表数据量非常大的话,直接从数据库查询数据,性能会非常差。可以使用Redis和memcached提升查询性能,从而提高接口性能。7、分库分表。当系统发展到一定的阶段,用户并发量大,会有大量的数据库请求,需要占用大量的数据库连接,同时会带来磁盘IO的性能瓶颈问题。或者数据库表数据非常大,SQL查询即使走了索引,也很耗时。这时,可以通过分库分表解决。分库用于解决数据库连接资源不足问题,和磁盘IO的性能瓶颈问题。分表用于解决单表数据量太大,sql语句查询数据时,即使走了索引也非常耗时问题。8、避免在循环中查询数据库。循环查询数据库,非常耗时,最好能在一次查询中获取所有需要的数据。为什么在阿里巴巴Java开发手册中强制要求使用包装类型定义属性呢?嗯,以布尔字段为例,当我们没有设置对象的字段的值的时候,Boolean类型的变量会设置默认值为null,而boolean类型的变量会设置默认值为false。也就是说,包装类型的默认值都是null,而基本数据类型的默认值是一个固定值,如boolean是false,byte、short、int、long是0,float是0.0f等。举一个例子,比如有一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。那我可以对0.0做特殊判断,如果是0就阻断报错,这样是否可以呢?不对,这时候就会产生一个问题,如果允许费率是0的场景又怎么处理呢?使用基本数据类型只会让方案越来越复杂,坑越来越多。这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。因此,建议在POJO和RPC的返回值中使用包装类型。8招让接口性能提升100倍池化思想如果你每次需要用到线程,都去创建,就会有增加一定的耗时,而线程池可以重复利用线程,避免不必要的耗时。比如TCP三次握手,它为了减少性能损耗,引入了Keep-Alive长连接,避免频繁的创建和销毁连接。拒绝阻塞等待如果你调用一个系统B的接口,但是它处理业务逻辑,耗时需要10s甚至更多。然后你是一直阻塞等待,直到系统B的下游接口返回,再继续你的下一步操作吗?这样显然不合理。参考IO多路复用模型。即我们不用阻塞等待系统B的接口,而是先去做别的操作。等系统B的接口处理完,通过事件回调通知,我们接口收到通知再进行对应的业务操作即可。远程调用由串行改为并行比如设计一个商城首页接口,需要查商品信息、营销信息、用户信息等等。如果是串行一个一个查,那耗时就比较大了。这种场景是可以改为并行调用的,降低接口耗时。锁粒度避免过粗在高并发场景,为了防止超卖等情况,我们经常需要加锁来保护共享资源。但是,如果加锁的粒度过粗,是很影响接口性能的。不管你是synchronized加锁还是redis分布式锁,只需要在共享临界资源加锁即可,不涉及共享资源的,就不必要加锁。耗时操作,考虑放到异步执行耗时操作,考虑用异步处理,这样可以降低接口耗时。比如用户注册成功后,短信邮件通知,是可以异步处理的。使用缓存把要查的数据,提前放好到缓存里面,需要时,直接查缓存,而避免去查数据库或者计算的过程。提前初始化到缓存预取思想很容易理解,就是提前把要计算查询的数据,初始化到缓存。如果你在未来某个时间需要用到某个经过复杂计算的数据,才实时去计算的话,可能耗时比较大。这时候,我们可以采取预取思想,提前把将来可能需要的数据计算好,放到缓存中,等需要的时候,去缓存取就行。这将大幅度提高接口性能。压缩传输内容压缩传输内容,传输报文变得更小,因此传输会更快。参考:https://juejin.cn/post/7167153109158854687最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~Github地址:https://github.com/Tyson0314/java-books
文章
存储  ·  缓存  ·  安全  ·  Java  ·  编译器  ·  应用服务中间件  ·  数据库  ·  C++  ·  索引  ·  容器
2023-02-16
一次性弄清楚 Authentication & Authorization 以及 Cookie、Session、Token
认证和授权的区别是什么?不出意外情况,我想这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。说简单点就是:认证 (Authentication): 你是谁?Who are you?授权 (Authorization): 你有权限干什么?What do you have access to do?稍微正式点(啰嗦点)的说法就是:Authentication(认证) 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。Authorization(授权) 发生在 Authentication(认证)之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。举个简单的例子:比如工作牌就是 Authentication(认证)你是谋谋公司,机构或组织的身份标识和凭据,根据这个凭据即可进入该单位区域工作,比如刷卡进出大门;而你个人的工作岗位和职责就类似Authorization(授权),单位授权该岗位能干些什么范围的工作;重新认识 Cookie什么是 Cookie,以及它的作用是什么?Cookie 有时也用其复数形式 Cookies,存储类型为“小型文本文件”,是某些网站为了辨别用户身份,进行 Session 跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。Cookie 特点:存储结构:以 Key-Value 形式存储的“小型文本文件”;存储位置:存储在用户终端的某个目录下;存储大小:≤ 4KB;时效性:可暂时或永久性保存,用户终端自行决定;Cookie是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。属性说明Name/Value设置 Cookie 的名称及相对应的值,对于认证 Cookie,Value 值包括 Web 服务器所提供的访问令牌。Expires设置 Cookie 的生存期。 有两种存储类型的 Cookie:会话性与持久性。Expires 属性缺省时,为会话性 Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性 Cookie 会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效。Path定义了 Web 站点上可以访问该 Cookie 的目录。Domain指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie 受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置 .org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。Secure指定是否使用 HTTPS 安全协议发送 Cookie。使用 HTTPS 安全协议,可以保护 Cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。该方法也可用于 Web 站点的身份鉴别,即在 HTTPS 的连接建立阶段,浏览器会检查 Web 网站的 SSL 证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到 SSL 证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到 Pharming 攻击所伪造的网站。HTTPOnly用于防止客户端脚本通过 document.cookie 属性访问 Cookie ,有助于保护 Cookie 不被跨站脚本攻击窃取或篡改。但是,HTTPOnly 的应用仍存在局限性,一些浏览器可以阻止客户端脚本对 Cookie 的读操作,但允许写操作;此外大多数浏览器仍允许通过 XMLHTTP 对象读取 HTTP 响应中的 Set-Cookie 头。如何使用 Cookie ?HTTP 协议中的 Cookie 包括 Web Cookie 和 浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。HTTP Cookie 机制是 HTTP 协议无状态的一种补充和改良。Cookie 主要用于下面三个目的:会话管理:登陆、购物车、游戏得分或者服务器应该记住的其他内容;个性化:用户偏好、主题或者其他设置;Session 追踪:记录和分析用户行为;Cookie 应用:创建 Cookie:当接收到客户端发出的 HTTP 请求时,服务器可以发送带有响应的标头,Cookie 通常由浏览器存储,然后将 Cookie 与 HTTP 标头一同向服务器发出请求。Set-Cookie 和 Cookie 标头:Set-Cookie 标头告诉客户端存储 Cookie,响应标头将 cookie 从服务器发送到用户代理;客户端请求则使用 Cookie 头将存储的 Cookie 发送回服务器;下面是 Cookie 的一些应用案例:我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。使用 Cookie 保存 Session 或者 Token ,向后端发送请求的时候带上 Cookie,这样后端就能取到 Session 或者 token 了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在Cookie。什么是 Session ?Session 简介在计算机中,尤其是在网络应用中,通常称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 的主要作用就是通过服务端记录用户的状态。用于保持状态的基于 Web 服务器的方法。Session 允许通过将对象存储在 Web 服务器的内存中在整个用户会话过程中保持任何对象。Session 的一些应用案例:存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。Cookie 和 Session 有什么区别?由于 HTTP 协议无状态的缺陷。WEB 的设计者们提出了 Cookie 和 Session 两种解决机制。通过对两者的比较分析,指出了它们的区别与联系。——CookieSession效期暂时和永久常规暂时(也可实现永久)存储用户终端/客户端浏览器服务器(Session + SessionID)结构key-valuekey-value优点1.极高的扩展性和可用性。 2.通过良好的编程,控制保存在cookie中的session对象的大小。 3.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。 4.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。 5.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。 1.如果要在诸多Web页间传递一个变量,那么用Session变量要比通过QueryString传递变量可使问题简化。 2.要使WEb站点具有用户化,可以考虑使用Session变量。你的站点的每位访问者都有用户化的经验,基于此,随着LDAP和诸如MS Site 3.Server等的使用,已不必再将所有用户化过程置入Session变量了,而这个用户化是取决于用户喜好的。 4.你可以在任何想要使用的时候直接使用session变量,而不必事先声明它,这种方式接近于在VB中变量的使用。使用完毕后,也不必考虑将其释放,因为它将自动释放。 5.Session实例是轻量级的,所谓轻量级:是指他的创建和删除不需要消耗太多资源; 功能缺陷1.Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。 2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。 3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。 1.进程依赖性,ASP Session状态存于IIS的进程中,也就是inetinfo.exe这个程序。所以当inetinfo.exe进程崩溃时,这些信息也就丢失。另外,重起或者关闭IIS服务都会造成信息的丢失。 2.CORS(跨域资源共享):Session状态使用范围的局限性,当一个用户从一个网站访问到另外一个网站时,这些Session信息并不会随之迁移过去。 3.存在Cookie的依赖性,实际上客户端的Session信息是存储在Cookie中的,如果客户端完全禁用掉了Cookie功能,也就不能享受到了Session提供的功能了。 4.每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加; 5.不是线程安全的,应该避免多个线程共享同一个Session实例。 安全 安全性相对较低; 1.Cookie捕获/重放; 2.恶意 Cookies; 3.会话定置(Session Fixation)攻击; 4.跨站请求伪造(Cross-Site Request Forgery,简称CSRF)攻击; 安全性相对较高; 1.web服务器防护; 2.主机安全防护; 3.跨站请求伪造(Cross-Site Request Forgery,简称CSRF)攻击; 因为创建 Session 变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以过度使用 Session 变量将会导致代码可读性降低,使项目维护困难。理解 SessionID 的本质客户端使用 Cookie 保存 SessionID客户端用 Cookie 保存了 SessionID,当我们请求服务器的时候,会把这个 SessionID 一起发给服务器,服务器会到内存中搜索对应的 SessionID,如果找到了对应的 SessionID,说明我们处于登录状态,有相应的权限;如果没有找到对应的 SessionID,这说明:要么是我们把浏览器关掉了(后面会说明为什么),要么 session 超时了(没有请求服务器超过 20 分钟),session 被服务器清除了,则服务器会给你分配一个新的 SessionID。你得重 新登录并把这个新的 SessionID 保存在 Cookie 中。 在没有把浏览器关掉的时候(这个时候假如已经把 SessionID 保存在 Cookie 中了)这个 SessionID 会一直保存在浏览器中,每次请求的时候都会把这个 SessionID 提交到服务器,所以服务器认为我们是登录的;当然,如果太长时间没有请求服务器,服务器会认为我们已经所以把浏览器关掉了,这个时候服务器会把该 SessionID 从内存中清除掉,这个时候如果我们再去请求服务器,SessionID 已经不存在了,所以服务器并没有在内存中找到对应的 SessionID,所以会再产生一个新的 SessionID,这个时候一般我们又要再登录一次。客户端未使用 Cookie 保存 SessionID此时如果我们请求服务器,因为没有提交 SessionID 上来,服务器会认为你是一个全新的请求,服务器会给你分配一个新的 SessionID,这就是为什么我们每次打开一个新的浏览器的时候(无论之前我们有没有登录过)都会产生一个新的 SessionID(或者是会让我们重新登录)。当我们一旦把浏览器关掉后,再打开浏览器再请求该页面,它会让我们登录,这是为什么?我们明明已经登录了,而且还没有超时,SessionID 肯定还在服 务器上的,为什么现在我们又要再一次登录呢?这是因为我们关掉浏览再请求的时候,我们提交的信息没有把刚才的 SessionID 一起提交到服务器,所以服务器不知道我们是同一个人,所以这时服务器又为我们分配一个新的 SessionID。打个比方:浏览器就好像一个要去银行开户的人,而服务器就好比银行, 这个要去银行开户的人这个时候显然没有帐号( SessionID),所以到银行后,银行工作人员问有没有帐号,他说没有,这个时候银行就会为他开通一个帐号。所以可以这么说,每次打开一个新的浏览器去请求的一个页面的时候,服务器都会认为,这是一个新的请求,他为你分配一个新的 SessionID。基于以上问题,于是有人就会思考,服务器为什么要保存这些信息呢, 只让每个客户端去保存该多好?服务端只需把关好验证即可,因此在这种情况下,Token 应用而生。关于 Token 的理解什么是 Token ?Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。Token 的引入:Token 是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token 便应运而生。Token 的定义:Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次带上用户名和密码。使用 Token 的目的:Token 的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。什么是 JWT?说到 Token 我们不得不谈 JWT,Why...? JWT 是 JSON Web Token 的缩写,是目前最流行的跨域认证解决方案。关于跨域认证的问题互联网服务离不开用户认证。一般流程是下面这样。用户向服务器发送用户名和密码。服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。服务器向用户返回一个 session_id,写入用户的 Cookie。用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。举例说明:A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。JWT 的原理JWT 的原理是,服务器认证以后,生成一个 Base64URL 编码后的 JSON 对象,发回给用户,JSON 明文信息如下(后面叙述)。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。JWT 的数据结构Header(头部):是一个 JSON 对象,描述 JWT 的元数据;Payload(负载):也是一个 JSON 对象,用来存放实际需要传递的数据;Signature(签名):对前两部分的签名,防止数据篡改;说明:JWT 实际是一个很长的字符串,分别由【Header.Payload.Signature】组成,注意中间使用【.】分隔成三个部分。Header(头部)通常如下对象:{ "alg": "HS256", //alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256) "typ": "JWT" //typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT }Payload(负载)JWT 规定了 7 个官方字段,供选用:iss (issuer):签发人exp (expiration time):过期时间sub (subject):主题aud (audience):受众nbf (Not Before):生效时间iat (Issued At):签发时间jti (JWT ID):编号除了上面官方规定的字段,你还可以在这个部分自定义私有字段,下面就是一个例子:{ "sub": "1234567890", "name": "chait", "admin": true }注意:JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。Signature(签名)首先,需要指定一个密钥(secret),这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)算出签名以后,把 Header(Base64URL编码)、Payload(Base64URL编码)、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。Base64URL 算法前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同,区别如下:JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。JWT 的使用方式客户端接收到服务器返回的 JWT,可以存储在 Cookie 或 Local Storage;客户端每次与服务器通信,都要带上这个 JWT;你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,针对跨域提供两种方案:更好的做法是放在 HTTP 请求的头信息【Authorization】字段里面或者是自定义约定字段;JWT 就放在 POST 请求的数据体里面;JWT 的几个特点(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。基于 Token 的身份验证原理基于 Token 的身份验证是无状态的,我们不用将用户信息存在服务器或 Session 中。这种概念解决了在服务端存储信息时的许多问题。没有 session 信息意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录和已经登录到了哪里。虽然基于 Token 的身份验证实现的方式很多,但大致过程如下:用户通过用户名和密码发送请求。程序验证。程序返回一个签名的 token 给客户端。客户端储存 token, 并且每次请求都会附带它。服务端验证 token 并返回数据。每一次请求都需要 Token。Token 应该在 HTTP的头部发送从而保证了 Http 请求无状态。我们也需要设置服务器属性 【Access-Control-Allow-Origin: * 】来让服务器能接受到来自所有域的请求。需要注意的是,在 ACAO 头部指定 * 时,不得带有像 HTTP 认证,客户端 SSL 证书和 cookies 的证书。执行流程如下:执行流程说明:用户登录校验,校验成功后就返回 Token 给客户端。客户端收到 Token 以后可以把它存储起来,比如放在 localStorage 中。客户端每次访问 API 都(通常 http 请求头)携带 Token 到服务器端。服务器端采用 filter 过滤器校验。校验成功则返回请求数据,校验失败则返回错误码。当我们在程序中认证了信息并取得 token 之后,我们便能通过这个 token 做许多的事情。我们甚至能基于创建一个基于权限的 token 传给第三方应用程序,这些第三方程序能够获取到我们的数据(当然只限于该 token 被允许访问的数据)。Tokens 的优势和缺陷那么相对于 Cookie 和 Session,Token 有哪些优缺点呢?1、Token 的优势支持跨域访问: Cookie 是不允许垮域访问的,token 支持;无状态、可扩展: token 无状态,session 有状态的;去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候, 你可以进行 Token 生成调用即可.多平台支持: Cookie 不支持手机端访问的,token 支持,更适用于移动应用;基于标准化: 你的 API 可以采用标准化的 JSON Web Token (JWT)。这个标准已经存在多个后端库(.NET,Ruby,Java,Python,PHP 等)和多家公司的支持(如:Firebase,Google,Microsoft 等);2、Token 的缺陷带宽占用:正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多。安全隐患:无法在服务端注销,服务器一旦生产 Token 并下发客户端,在 Token 有效期内很难解决劫持问题。性能问题:JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。听着似乎很牛逼,但是没有任何优势,为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。关于 JWT、JWS 与 JWE 的区别,此处不再详述,请自行查看 => https://blog.csdn.net/ChaITSimpleLove/article/details/120178667
文章
存储  ·  JSON  ·  安全  ·  算法  ·  前端开发  ·  网络安全  ·  API  ·  数据库  ·  数据安全/隐私保护  ·  数据格式
2023-03-04
用FastDFS一步步搭建文件管理系统 上
一、FastDFS介绍FastDFS开源地址:https://github.com/happyfish100参考:分布式文件系统FastDFS设计原理  参考:FastDFS分布式文件系统个人封装的FastDFS Java API:https://github.com/bojiangzhou/lyyzoo-fastdfs-java1、简介FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。FastDFS 系统有三个角色:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。  Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。  Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。  Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。2、FastDFS的存储策略为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。3、FastDFS的上传过程FastDFS向使用者提供基本文件访问接口,比如upload、download、append、delete等,以客户端库的方式提供给用户使用。Storage Server会定期的向Tracker Server发送自己的存储信息。当Tracker Server Cluster中的Tracker Server不止一个时,各个Tracker之间的关系是对等的,所以客户端上传时可以选择任意一个Tracker。当Tracker收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的group,当选定了group后就要决定给客户端分配group中的哪一个storage server。当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录。然后为文件分配一个fileid,最后根据以上的信息生成文件名存储文件。4、FastDFS的文件同步写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由后台线程将文件同步至同group内其他的storage server。每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。5、FastDFS的文件下载客户端uploadfile成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。跟upload file一样,在downloadfile时客户端可以选择任意tracker server。tracker发送download请求给某个tracker,必须带上文件名信息,tracke从文件名中解析出文件的group、大小、创建时间等信息,然后为该请求选择一个storage用来服务读请求。二、安装FastDFS环境0、前言操作环境:CentOS7 X64,以下操作都是单机环境。我把所有的安装包下载到/softpackages/下,解压到当前目录。先做一件事,修改hosts,将文件服务器的ip与域名映射(单机TrackerServer环境),因为后面很多配置里面都需要去配置服务器地址,ip变了,就只需要修改hosts即可。# vim /etc/hosts增加如下一行,这是我的IP192.168.51.128 file.ljzsg.com如果要本机访问虚拟机,在C:\Windows\System32\drivers\etc\hosts中同样增加一行1、下载安装 libfastcommonlibfastcommon是从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库,基础环境,安装即可 。① 下载libfastcommon# wget https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz② 解压# tar -zxvf V1.0.7.tar.gz# cd libfastcommon-1.0.7③ 编译、安装# ./make.sh# ./make.sh install④ libfastcommon.so 安装到了/usr/lib64/libfastcommon.so,但是FastDFS主程序设置的lib目录是/usr/local/lib,所以需要创建软链接。# ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so# ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so# ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so# ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so  2、下载安装FastDFS① 下载FastDFS# wget https://github.com/happyfish100/fastdfs/archive/V5.05.tar.gz② 解压# tar -zxvf V5.05.tar.gz# cd fastdfs-5.05③ 编译、安装# ./make.sh# ./make.sh install④ 默认安装方式安装后的相应文件与目录  A、服务脚本:/etc/init.d/fdfs_storaged/etc/init.d/fdfs_tracker  B、配置文件(这三个是作者给的样例配置文件) :/etc/fdfs/client.conf.sample /etc/fdfs/storage.conf.sample /etc/fdfs/tracker.conf.sample C、命令工具在 /usr/bin/ 目录下:fdfs_appender_test fdfs_appender_test1 fdfs_append_file fdfs_crc32 fdfs_delete_file fdfs_download_file fdfs_file_info fdfs_monitor fdfs_storaged fdfs_test fdfs_test1 fdfs_trackerd fdfs_upload_appender fdfs_upload_file stop.sh restart.sh⑤ FastDFS 服务脚本设置的 bin 目录是 /usr/local/bin, 但实际命令安装在 /usr/bin/ 下。  两种方式:  》 一是修改FastDFS 服务脚本中相应的命令路径,也就是把 /etc/init.d/fdfs_storaged 和 /etc/init.d/fdfs_tracker 两个脚本中的 /usr/local/bin 修改成 /usr/bin。     # vim fdfs_trackerd    使用查找替换命令进统一修改:%s+/usr/local/bin+/usr/bin    # vim fdfs_storaged    使用查找替换命令进统一修改:%s+/usr/local/bin+/usr/bin》 二是建立 /usr/bin 到 /usr/local/bin 的软链接,我是用这种方式。  # ln -s /usr/bin/fdfs_trackerd   /usr/local/bin# ln -s /usr/bin/fdfs_storaged   /usr/local/bin# ln -s /usr/bin/stop.sh         /usr/local/bin# ln -s /usr/bin/restart.sh      /usr/local/bin3、配置FastDFS跟踪器(Tracker)配置文件详细说明参考:FastDFS 配置文件详解① 进入 /etc/fdfs,复制 FastDFS 跟踪器样例配置文件 tracker.conf.sample,并重命名为 tracker.conf。# cd /etc/fdfs# cp tracker.conf.sample tracker.conf# vim tracker.conf② 编辑tracker.conf ,标红的需要修改下,其它的默认即可。# 配置文件是否不生效,false 为生效disabled=false# 提供服务的端口port=22122# Tracker 数据和日志目录地址(根目录必须存在,子目录会自动创建)base_path=/ljzsg/fastdfs/tracker# HTTP 服务端口http.server_port=80③ 创建tracker基础数据目录,即base_path对应的目录# mkdir -p /ljzsg/fastdfs/tracker④ 防火墙中打开跟踪端口(默认的22122)# vim /etc/sysconfig/iptables添加如下端口行:-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT重启防火墙:# service iptables restart⑤ 启动Tracker初次成功启动,会在 /ljzsg/fdfsdfs/tracker/ (配置的base_path)下创建 data、logs 两个目录。可以用这种方式启动# /etc/init.d/fdfs_trackerd start也可以用这种方式启动,前提是上面创建了软链接,后面都用这种方式# service fdfs_trackerd start查看 FastDFS Tracker 是否已成功启动 ,22122端口正在被监听,则算是Tracker服务安装成功。# netstat -unltp|grep fdfs关闭Tracker命令:# service fdfs_trackerd stop⑥ 设置Tracker开机启动# chkconfig fdfs_trackerd on或者:# vim /etc/rc.d/rc.local加入配置:/etc/init.d/fdfs_trackerd start  ⑦ tracker server 目录及文件结构Tracker服务启动成功后,会在base_path下创建data、logs两个目录。目录结构如下:${base_path}  |__data  |   |__storage_groups.dat:存储分组信息  |   |__storage_servers.dat:存储服务器列表  |__logs  |   |__trackerd.log: tracker server 日志文件  4、配置 FastDFS 存储 (Storage)① 进入 /etc/fdfs 目录,复制 FastDFS 存储器样例配置文件 storage.conf.sample,并重命名为 storage.conf# cd /etc/fdfs# cp storage.conf.sample storage.conf# vim storage.conf② 编辑storage.conf标红的需要修改,其它的默认即可。# 配置文件是否不生效,false 为生效disabled=false  # 指定此 storage server 所在 组(卷)group_name=group1# storage server 服务端口port=23000# 心跳间隔时间,单位为秒 (这里是指主动向 tracker server 发送心跳)heart_beat_interval=30# Storage 数据和日志目录地址(根目录必须存在,子目录会自动生成)base_path=/ljzsg/fastdfs/storage# 存放文件时 storage server 支持多个路径。这里配置存放文件的基路径数目,通常只配一个目录。store_path_count=1# 逐一配置 store_path_count 个路径,索引号基于 0。# 如果不配置 store_path0,那它就和 base_path 对应的路径一样。store_path0=/ljzsg/fastdfs/file# FastDFS 存储文件时,采用了两级目录。这里配置存放文件的目录个数。  # 如果本参数只为 N(如: 256),那么 storage server 在初次运行时,会在 store_path 下自动创建 N * N 个存放文件的子目录。subdir_count_per_path=256# tracker_server 的列表 ,会主动连接 tracker_server# 有多个 tracker server 时,每个 tracker server 写一行tracker_server=file.ljzsg.com:22122# 允许系统同步的时间段 (默认是全天) 。一般用于避免高峰同步产生一些问题而设定。sync_start_time=00:00sync_end_time=23:59# 访问端口http.server_port=80③ 创建Storage基础数据目录,对应base_path目录# mkdir -p /ljzsg/fastdfs/storage# 这是配置的store_path0路径# mkdir -p /ljzsg/fastdfs/file④ 防火墙中打开存储器端口(默认的 23000)# vim /etc/sysconfig/iptables添加如下端口行:-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT重启防火墙:# service iptables restart⑤ 启动 Storage启动Storage前确保Tracker是启动的。初次启动成功,会在 /ljzsg/fastdfs/storage 目录下创建 data、 logs 两个目录。可以用这种方式启动# /etc/init.d/fdfs_storaged start也可以用这种方式,后面都用这种# service fdfs_storaged start查看 Storage 是否成功启动,23000 端口正在被监听,就算 Storage 启动成功。# netstat -unltp|grep fdfs关闭Storage命令:# service fdfs_storaged stop查看Storage和Tracker是否在通信:/usr/bin/fdfs_monitor /etc/fdfs/storage.conf⑥ 设置 Storage 开机启动# chkconfig fdfs_storaged on或者:# vim /etc/rc.d/rc.local加入配置:/etc/init.d/fdfs_storaged start⑦ Storage 目录同 Tracker,Storage 启动成功后,在base_path 下创建了data、logs目录,记录着 Storage Server 的信息。在 store_path0 目录下,创建了N*N个子目录:5、文件上传测试① 修改 Tracker 服务器中的客户端配置文件  # cd /etc/fdfs# cp client.conf.sample client.conf# vim client.conf修改如下配置即可,其它默认。# Client 的数据和日志目录base_path=/ljzsg/fastdfs/client# Tracker端口tracker_server=file.ljzsg.com:22122② 上传测试 在linux内部执行如下命令上传 namei.jpeg 图片# /usr/bin/fdfs_upload_file /etc/fdfs/client.conf namei.jpeg上传成功后返回文件ID号:group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg返回的文件ID由group、存储目录、两级子目录、fileid、文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。三、安装Nginx上面将文件上传成功了,但我们无法下载。因此安装Nginx作为服务器以支持Http方式访问文件。同时,后面安装FastDFS的Nginx模块也需要Nginx环境。Nginx只需要安装到StorageServer所在的服务器即可,用于访问文件。我这里由于是单机,TrackerServer和StorageServer在一台服务器上。1、安装nginx所需环境  ① gcc 安装# yum install gcc-c++② PCRE pcre-devel 安装# yum install -y pcre pcre-devel③ zlib 安装# yum install -y zlib zlib-devel④ OpenSSL 安装# yum install -y openssl openssl-devel2、安装Nginx① 下载nginx# wget -c https://nginx.org/download/nginx-1.12.1.tar.gz② 解压# tar -zxvf nginx-1.12.1.tar.gz# cd nginx-1.12.1③ 使用默认配置# ./configure④ 编译、安装# make# make install⑤ 启动nginx# cd /usr/local/nginx/sbin/# ./nginx  其它命令# ./nginx -s stop# ./nginx -s quit# ./nginx -s reload⑥ 设置开机启动# vim /etc/rc.local添加一行:/usr/local/nginx/sbin/nginx# 设置执行权限# chmod 755 rc.local⑦ 查看nginx的版本及模块/usr/local/nginx/sbin/nginx -V⑧ 防火墙中打开Nginx端口(默认的 80)  添加后就能在本机使用80端口访问了。# vim /etc/sysconfig/iptables添加如下端口行:-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT重启防火墙:# service iptables restart3、访问文件简单的测试访问文件① 修改nginx.conf# vim /usr/local/nginx/conf/nginx.conf添加如下行,将 /group1/M00 映射到 /ljzsg/fastdfs/file/datalocation /group1/M00 {    alias /ljzsg/fastdfs/file/data;}# 重启nginx# /usr/local/nginx/sbin/nginx -s reload② 在浏览器访问之前上传的图片、成功。http://file.ljzsg.com/group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg
文章
存储  ·  机器学习/深度学习  ·  负载均衡  ·  网络协议  ·  应用服务中间件  ·  Linux  ·  网络安全  ·  开发工具  ·  文件存储  ·  nginx
2023-02-26
构建LVS负载均衡集群
LVS即Linux虚拟服务器,目前 LVS 已经被集成到 Linux 内核模块中,该项目在 Linux 内核实现了基于 IP 的数据请求负载均衡调度方案,LVS集群采用IP负载均衡技术和基于内容请求分发技术.调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器.整个服务器集群的结构对客户是透明的,而且无需修改客户端和服务器端的程序.为此,在设计时需要考虑系统的透明性、可伸缩性、高可用性和易管理性.LVS(Linux Virtual Server) 的作用LVS主要用于服务器集群的负载均衡,它工作在网络层,可以实现高性能,高可用的服务器集群技术.它廉价,可把许多低性能的服务器组合在一起形成一个超级服务器.它易用,配置非常简单,且有多种负载均衡的方法.它稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果.另外可扩展性也非常好.LVS自从1998年开始,发展到现在已经是一个比较成熟的技术项目了.可以利用LVS技术实现高可伸缩的、高可用的网络服务,例如WWW服务、Cache服务、DNS服务、FTP服务、MAIL服务、视频/音频点播服务等等,有许多比较著名网站和组织都在使用LVS架设的集群系统.LVS的体系结构,使用LVS架设的服务器集群系统有三个部分组成:● 最前端的负载均衡层,用Load Balancer表示● 中间的服务器集群层,用Server Array表示● 最底端的数据共享存储层,用Shared Storage表示LVS(Linux Virtual Server) 负载均衡机制LVS是四层负载均衡,也就是说建立在OSI模型的第四层传输层之上,传输层上有我们熟悉的TCP/UDP,LVS支持TCP/UDP的负载均衡.因为LVS是四层负载均衡,因此它相对于其它高层负载均衡的解决办法,比如DNS域名轮流解析、应用层负载的调度、客户端的调度等,它的效率是非常高的.Load Balancing负载均衡,可以减轻单台服务器压力,不同节点之间相互独立,不共享任何资源.通过一定算法将客户端的访问请求平分到群集的各个节点上,充分利用每个节点的资源.负载均衡扩展了网络设备和服务器带宽,增加吞吐量,加强网络数据处理能力.负载均衡集群分类软件实现: LVS RAC MySQLProxy Nginx HaProxy硬件实现: F5 citrix array 深信服 梭子鱼负载均衡集群的区别1.触发条件不同四层负载均衡:工作在传输层,转发数据依靠的是三层的IP地址 和 四层的端口(PORT).七层负载均衡:工作在应用层,转发数据依靠URL或主机名.2.实现原理不同四层负载均衡:TCP连接建立一次,客户端和RS主机之间.七层负载均衡:TCP连接建立两次,第一次是客户端和负载调度器,第二次是负载调度器和RS主机.3.应用场景不同四层负载均衡:TCP应用为主或者ERP软件七层负载均衡:以HTTP协议为主,例如apache服务器4.安全性不同四层负载均衡:转发攻击七层负载均衡:拦截攻击IPVS:钩子函数,内核机制,在请求没有到达目的地址之前,捕获并取得优先控制权的函数.IPVSADM:工作在用户空间,负责为ipvs内核框架编写规则,定义谁是集群服务,谁是后端真实的服务器.负载调度算法分类轮询方式解释rr(轮循)从1开始到n结束wrr(加权轮循)按权重比例进行调度,权重越大,负责的请求越多.sh(源地址hash)实现会话绑定,保留之前建立的会话信息.将来自于同一个ip地址的请求发送给一个真实服务器.dh(目标地址hash)将同一个目标地址的请求,发送给同一个服务器节点.提高缓存命中率.LC(最少连接)将新的连接请求分配给当前连接数最少的服务器.公式:活动连接*256+非活动连接.WLC(加权最少连接)最少连接的特殊模式.公式:(活动连接*256+非活动连接)/权重SED(最短期望延迟)加权最少连接的特殊模式.公式:(活动连接 +1)*256/权重NQ(永不排队)sed的特殊模式,当某台真实服务器连接为0时,直接分配不计算.LBLC(基于局部性的最少连接)dh的特殊模式既要提高缓存命中率又要考虑连接数量.先根据请求的目标IP地址寻找最近的该目标IP地址所有使用的服务器,如果这台服务器依然可用,并且有能力处理该请求,调度器会尽量选择相同的服务器,否则会继续选择其它可行的服务器.LBLCR(带复制的基于局部性的最少连接)LBLCR=LBLC+缓存共享机制动态算法既要考虑算法本身,也要考虑服务器状态(原理:通过hash表记录连接状态active/inactive)静态算法只考虑算法本身,不考虑服务器状态.下面我们将依次配置三种模式的集群方案,并说明原理.LVS-NAT 模式NAT 模式简介NAT (Network Address Translation)即网络地址转换,其作用是通过数据报头的修改,使位于企业内部的私有IP可以访问外网,以及外部用户可以访问位于公司内部的私有IP主机.LVS-NAT工作模式拓扑结构如下图,所示LVS负载调度器使用两块网卡配置不同的IP地址,eth0,设置为私钥IP与内部网络通过交换设备相互连接, eth1设置为外网IP与外部网络联通.原理图解:a).客户端将请求交给负载调度器,客户端发出数据包,此时这个数据包的源ip为CIP,目标ip为VIP (集群ip).b).数据包到达负载调度器后,修改数据包的目标ip地址为真实服务器的ip,此时数据包的源ip为CIP,目标ip为RIP.c).将数据包发送给RS,之后RS响应将数据包发回给负载调度器,此时数据包的源ip为RIP,目标ip为CIP.d).负载调度器再转发时,会将源ip地址改为自己的VIP地址,然后再发给客户端,此时数据包的源ip为VIP,目标ip为CIP.LVS-NAT 模式特点:1.负载调度器和真实服务器,必须位于同一网络.2.真实服务器的网关必须指向DIP.3.负载调度器必须位于客户端和真实服务器之间.4.RIP通常都是私有地址,仅用于各个集群节点通信.5.支持端口映射,可映射为任意地址.6.真实服务器可以使用任意操作系统,负载调度器必须是LINUX系统.7.所有数据报文都要经过负载调度器、压力过大、最多10台RS.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [接入模式] LVS-NAT ens32 192.168.1.12 桥接 ens32 192.168.20.14 NAT Read-Ser1 ens32 192.168.20.15 NAT Read-Ser2 ens32 192.168.20.16 NAT配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.开启IP转发,让其具有转发数据包的能力.[root@localhost ~]# echo "1" > /proc/sys/net/ipv4/ip_forward [root@localhost ~]# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf [root@localhost ~]# sysctl –p net.ipv4.ip_forward=13.配置LVS-NAT调度器,访问本机的80端口自动负载均衡到15-16这两台机器上.[root@localhost ~]# ipvsadm -A -t 192.168.1.12:80 -s rr #指定网卡1地址 #------------------------------------------------------------------------------- #[参数说明] -A 添加规则 -t TCP 指定分发器VIP -s 指定调度算法 rr 代表轮询round-robin #------------------------------------------------------------------------------- [root@localhost ~]# ipvsadm -a -t 192.168.1.12:80 -r 192.168.20.15 -m [root@localhost ~]# ipvsadm -a -t 192.168.1.12:80 -r 192.168.20.16 -m #------------------------------------------------------------------------------- #[参数说明] -a 添加real-server地址 -r 指定real-server地址 -m 表示masquerade NAT方式的LVS #-------------------------------------------------------------------------------4.查看并保存结果,下面就是我们的配置结果啦.[root@localhost ~]# ipvsadm -L -n #查看规则 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.12:80 rr -> 192.168.20.15:80 Masq 1 0 0 -> 192.168.20.16:80 Masq 1 0 0 [root@localhost ~]# /sbin/ipvsadm-save #保存规则4.配置防火墙SNAT,指定NAT表的POSTROUTING.iptables -t nat -A POSTROUTING \ #指定NAT表的POSTROUTING -s 192.168.1.0/24 \ #指定内网的网段 -o eno16777728 \ #指定外网口网卡名称 -j SNAT \ #指定为SNAT --to-source 59.110.167.239 #指定外网卡的地址 [root@localhost ~]# iptables -t nat -L #查看添加的规则配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作1.安装测试Apache,后端的两台主机都要配置Apache.[root@localhost ~]# yum install -y httpd [root@localhost ~]# echo "web 1" > /var/www/html/index.html [root@localhost ~]# systemctl restart httpd2.RelServer节点指定网关,指向主调度器的eth1网口.[root@localhost ~]# route add default gw 192.168.20.14LVS-DR 模式LVS-DR模式简介DR模式也叫做直接路由模式,其体系如下图所示,该模式中LVS依然承载数据的入站点请求以及算法的合理选择,不同之处在于dR模式只负责接受请求不负责发送,真正发送请求的是后端的主机节点,这样就在一定程度上缓解了服务器的压力,但是本模式要求调度器与后端的主机必须在一个局域网中.原理图解:a)客户端发出数据包,源ip是CIP,目标ip是VIP.b)依靠路由把数据发送给负载调度器,负载调度器将数据包的源MAC地址修改为DIP的MAC地址,将目标MAC地址修改为RIP的MAC地址,此时源ip和目标ip均未修改.c)由于DS和RS在同一网络中,所以通过二层来传输,通过路由再将数据包发到RS.d)RS接收数据包,之后通过lo接口传送给eth0向外发出,此时的源ip是VIP,目标ip为CIP.e)最后通过路由的方式发给客户端.DR 模式特点:1.负载路由器和真实服务器,必须位于同一网络.2.真实服务器的网关必须指向路由器.3.负载调度只处理入站请求.4.RIP可以是私有地址,也可以是公网地址.5.真实服务器可以使用任意操作系统,负载调度器必须是LINUX系统.6.负载调度器压力较小,支持100台左右的RS.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [VIP/IO] [接入模式] LVS-DR ens32 192.168.1.5 192.168.1.10(VIP) 桥接 Read-Ser1 ens32 192.168.1.13 192.168.1.10(IO) 桥接 Read-Ser2 ens32 192.168.1.14 192.168.1.10(IO) 桥接 #提示: 如果是在真实环境中 RealServer 应把网关指向路由器eth1口配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.修改内核参数,防止相同网络地址广播冲突.[root@localhost ~]# echo "net.ipv4.conf.all.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.ens32.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.default.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# sysctl -p #刷新内核参数 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.ens32.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 [root@localhost ~]# modprobe ip_vs && echo "ok" || echo "error" #查看内核是否加载 ok3.配置一个网卡子接口,此处我们在ens32接口上配置一个子接口ens32:0,这个子接口也就是VIP的地址,并添加一条路由记录.[root@localhost ~]# ifconfig ens32 ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.5 netmask 255.255.255.0 broadcast 192.168.1.255 inet6 fe80::5fbb:43ab:da3:f589 prefixlen 64 scopeid 0x20<link> ether 00:0c:29:1e:14:e2 txqueuelen 1000 (Ethernet) RX packets 4597 bytes 5186429 (4.9 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1711 bytes 177437 (173.2 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 [root@localhost ~]# ifconfig ens32:0 192.168.1.10 netmask 255.255.255.0 #在ens32添加子接口,VIP的地址 [root@localhost ~]# ifconfig ens32:0 ens32:0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.10 netmask 255.255.255.0 broadcast 192.168.1.255 ether 00:0c:29:1e:14:e2 txqueuelen 1000 (Ethernet) [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens32 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32 [root@localhost ~]# route add -host 192.168.1.10 dev ens32 #在ens32上添加一条路由记录 [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens32 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32 192.168.1.10 0.0.0.0 255.255.255.255 UH 0 0 0 ens324.接下来配置我们的LVS负载调度器,指定VIP地址和使用轮询算法,并保存永久生效.[root@localhost ~]# ipvsadm -A -t 192.168.1.10:80 -s rr #添加虚拟服务指定VIP地址 #--------------------------------------------------------------------------------- #[参数说明] -A 添加规则 -t TCP 指定分发器VIP -s 指定调度算法 rr 代表轮询round-robin #--------------------------------------------------------------------------------- [root@localhost ~]# ipvsadm -a -t 192.168.1.10:80 -r 192.168.1.13:80 -g #针对虚拟服务添加RS节点 [root@localhost ~]# ipvsadm -a -t 192.168.1.10:80 -r 192.168.1.14:80 -g #针对虚拟服务添加RS节点 #--------------------------------------------------------------------------------- #[参数说明] -a 添加real-server地址 -r 指定real-server地址 -m 以NAT模式分配 -g 以DR模式分配 -w 指定权值 #---------------------------------------------------------------------------------5.检查LVS的轮询配置规则,并保存配置文件,此处记得放行放火墙规则.[root@localhost ~]# ipvsadm -L -n --stats #查看VIP和RS是否已经配置成功 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes -> RemoteAddress:Port TCP 192.168.1.10:80 0 0 0 0 0 -> 192.168.1.13:80 0 0 0 0 0 -> 192.168.1.14:80 0 0 0 0 0 [root@localhost ~]# /sbin/ipvsadm-save #保存规则配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作如果是图形界面,需要关闭图形管理工具 systemctl stop NetworkManager1.首先关闭ARP宣告,和ARP转发,这里有两种方法,临时关闭与永久关闭.#-------------------------------------------------------------------------------- #[临时关闭] [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/all/arp_announce [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/all/arp_ignore #-------------------------------------------------------------------------------- #-------------------------------------------------------------------------------- #[永久关闭] [root@localhost ~]# vim /etc/sysctl.conf net.ipv4.conf.ens32.arp_ignore=1 net.ipv4.conf.ens32.arp_announce=2 net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2 [root@localhost ~]# sysctl -p #--------------------------------------------------------------------------------2.添加本地回环口,并设置24位的掩码,添加本地回环路由记录,这里设置成VIP的地址.[root@localhost ~]# ifconfig lo:0 192.168.1.10 netmask 255.255.255.255 #添加本地回环口,设置24位掩码 [root@localhost ~]# route add -host 192.168.1.10 dev lo #添加路由记录LVS-TUN 模式LVS-IP-TUN 模式简介IP-TUN也叫做IP隧道模式,它的配置不受空间的限制,TUN模式的思路就是将请求与相应数据分离,让调度器仅仅处理数据请求,而让真是服务器返回数据包,但是IP隧道模式由于物理主机不在一起,效率比较低下.原理图解:a)客户端发送数据包到负载调度器,此时数据包的源ip为CIP,目标ip为VIP.b)负载调度器会在数据包的外面再次封装一层ip数据包,封装源ip为DIP,目标ip为RIP.此时源ip为DIP,目标ip为RIP.c)之后负载调度器将数据包发给RS(因为在外层封装多了一层ip首部,所以可以理解为此时通过隧道传输),此时源ip为DIP,目标ip为RIP.d)RS接收到报文后发现是自己的IP地址,就将报文接收下来拆除掉最外层的IP后会发现里面还有一层IP首部,而且目标是自己的lo接口VIP,那么此时RS开始处理此请求,处理完成之后通过lo接口送给eth0网卡,然后向外传递.此时的源IP地址为VIP,目标IP为CIP.e)之后将数据包发给客户端.IP-TUN隧道 模式特点:1.所有真实服务器节点既要有RIP,又要有VIP,并且RIP、必须是公网ip.2.负载调度器和真实服务器必须支持隧道功能.3.负载调度器只处理入站请求.4.真实服务器一定不能使用负载雕塑群做默认网关.5.不支持端口映射.6.可跨互联网搭建集群,对网络环境要求较高.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [VIP/Tunl] [接入模式] LVS-IPTUN eno16777728 200.168.10.1 200.168.10.10(VIP) 外网IP Read-Ser1 eno16777728 200.168.10.2 200.168.10.10(Tunl) 外网IP Read-Ser2 eno16777728 200.168.10.3 200.168.10.10(Tunl) 外网IP配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.修改内核参数,防止相同网络地址广播冲突.[root@localhost ~]# echo "net.ipv4.conf.all.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.ens32.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.default.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# sysctl -p #刷新内核参数 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.ens32.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 [root@localhost ~]# modprobe ip_vs && echo "ok" || echo "error" #查看内核是否加载 ok3.LVS服务器配置虚拟IP,虚拟一个隧道IP,4个255代表它自己一个网段,把网段添加到路由表防止走200.168.10.0网段.[root@localhost ~]# ifconfig tunl0 200.168.10.10 netmask 255.255.255.255 up #虚拟一个隧道IP 4个255代表它自己一个网段 [root@localhost ~]# route add -host 200.168.10.10 dev tunl0 #把网段添加到路由表 防止走 200.168.10.0 网段 [root@localhost ~]# route -n #查看路由2.设置LVS调度器.[root@localhost ~]# ipvsadm -C [root@localhost ~]# ipvsadm -A -t 200.168.10.10:80 -s rr [root@localhost ~]# ipvsadm -a -t 200.168.10.10:80 -r 200.168.10.2 -i [root@localhost ~]# ipvsadm -a -t 200.168.10.10:80 -r 200.168.10.3 -i [root@localhost ~]# ipvsadm -L -n --stat配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作1.配置网卡子接口.[root@localhost ~]# ifconfig tunl0 200.168.10.10 netmask 255.255.255.255 up [root@localhost ~]# route add -host 200.168.10.10 dev tunl02.修改环境变量,写入配置文件开机自动加载.[root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/tunl0/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/tunl0/arp_announce [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce3.访问集群IP,测试隧道效果.[root@localhost ~]# elinks http://200.168.10.10
文章
缓存  ·  负载均衡  ·  网络协议  ·  算法  ·  Linux  ·  网络安全  ·  调度  ·  Apache  ·  网络架构  ·  Perl
2023-02-26
SpringCloudAlibaba 学习笔记
❤ 作者主页:欢迎来到我的技术博客❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~* 如果文章对您有帮助,记得关注、点赞、收藏、评论⭐️⭐️⭐️ 您的支持将是我创作的动力,让我们一起加油进步吧!!!第一章 SpringCloud Alibaba入门简介一、为什么使用Alibaba因为:spring netflix进入维护模式https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now#spring-cloud-netflix-projects-entering-maintenance-mode什么是维护模式: spring cloud团队将不会再向模块添加新功能,我们将修复block级别的bug以及安全问题,我们也会考虑并审查社区的小型pull request。我们打算继续支持这些模块,知道Greenwich版本被普遍采用至少一年。SpringCloud Netflix将不再开发新的组件。以下spring cloud netflix模块和响应的starter将进入维护模式:spring-cloud-netflix-archaiusspring-cloud-netflix-hystrix-contractspring-cloud-netflix-hystrix-dashboardspring-cloud-netflix-hystrix-streamspring-cloud-netflix-hystrixspring-cloud-netflix-ribbonspring-cloud-netflix-turbine-streamspring-cloud-netflix-turbinespring-cloud-netflix-zuul我们都知道SpringCloud版本迭代是比较快的,因而出现了很多重大ISSUE都还来不及Flix就又推另一个RELEASE了。进入维护模式意思就是目前以及以后一段时间SpingCloud Netflix提供的报务和功能就这么多了,不在开发新的组件和功能了。以后将以雏护和Merge分支Full Request为主。所以:spring cloud alibaba来了官方文档: https://spring.io/projects/spring-cloud-alibaba主要功能:服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。组件:Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。二、如何使用?Spring官网:https://spring.io/projects/spring-cloud-alibabaGitHub:https://github.com/alibaba/spring-cloud-alibabaGitHub中文文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.mdSpring Cloud Alibaba参考文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html按照官网一步一步来:https://spring.io/projects/spring-cloud-alibaba#learn新建父工程 spring-cloud-alibaba父工程引入:<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2021.0.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>三、版本对应https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8ESpring Cloud Alibaba VersionSentinel VersionNacos VersionRocketMQ VersionDubbo VersionSeata Version2021.0.1.0*1.8.31.4.24.9.22.7.151.4.2Spring Cloud Alibaba VersionSpring Cloud VersionSpring Boot Version2021.0.1.0Spring Cloud 2021.0.12.6.3第二章 服务注册和配置中心一、是什么官方文档:https://nacos.io/zh-cn/docs/what-is-nacos.html1. nacos(NAming COnfiguration Service):服务注册和配置中心Nacos = Eureka + Config + Bus 替代Eureka做服务注册中心 替代Config做服务配置中心github地址: https://github.com/alibaba/NacosNacos地址: https://nacos.io/zh-cn/2. Nacos 概念地域 物理的数据中心,资源创建成功后不能更换。可用区 同一地域内,电力和网络互相独立的物理区域。同一可用区内,实例的网络延迟较低。接入点 地域的某个服务的入口域名。命名空间 用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。配置 在系统开发过程中,开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成。配置变更是调整系统运行时的行为的有效手段。配置管理 系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动。配置项 一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。配置集 一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。配置集 ID Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。配置分组 Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。配置快照 Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。配置快照类似于 Git 中的本地 commit,也类似于缓存,会在适当的时机更新,但是并没有缓存过期(expiration)的概念。服务 通过预定义接口网络访问的提供给客户端的软件功能。服务名 服务提供的标识,通过该标识可以唯一确定其指代的服务。服务注册中心 存储服务实例和服务负载均衡策略的数据库。服务发现 在计算机网络上,(通常使用服务名)对服务下的实例的地址和元数据进行探测,并以预先定义的接口提供给客户端进行查询。元信息 Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。应用 用于标识服务提供方的服务的属性。服务分组 不同的服务可以归类到同一分组。虚拟集群 同一个服务下的所有服务实例组成一个默认集群, 集群可以被进一步按需求划分,划分的单位可以是虚拟集群。实例 提供一个或多个服务的具有可访问网络地址(IP:Port)的进程。权重 实例级别的配置。权重为浮点数。权重越大,分配给该实例的流量越大。健康检查 以指定方式检查服务下挂载的实例 (Instance) 的健康度,从而确认该实例 (Instance) 是否能提供服务。根据检查结果,实例 (Instance) 会被判断为健康或不健康。对服务发起解析请求时,不健康的实例 (Instance) 不会返回给客户端。健康保护阈值 为了防止因过多实例 (Instance) 不健康导致流量全部流向健康实例 (Instance) ,继而造成流量压力把健康实例 (Instance) 压垮并形成雪崩效应,应将健康保护阈值定义为一个 0 到 1 之间的浮点数。当域名健康实例数 (Instance) 占总服务实例数 (Instance) 的比例小于该值时,无论实例 (Instance) 是否健康,都会将这个实例 (Instance) 返回给客户端。这样做虽然损失了一部分流量,但是保证了集群中剩余健康实例 (Instance) 能正常工作。3. 架构数据模型Nacos 数据模型 Key 由三元组唯一确定, Namespace默认是空串,公共命名空间(public),分组默认是 DEFAULT_GROUP。服务领域模型配置领域模型围绕配置,主要有两个关联的实体,一个是配置变更历史,一个是服务标签(用于打标分类,方便索引),由 ID 关联。二、Nacos2.0新增长连接功能Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。新增 鉴权插件新增 配置加密三、与其他注册中心对比服务注册与服务框架CAP模型控制台管理社区活跃度EurekaAP高可用支持低(2.x版本闭源)ZookeeperCP一致支持中ConsulCP支持高NacosAP+CP支持高四、切换nacos可以切换 AP 和 CP ,可使用如下命令切换成CP模式。curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'五、下载下载地址:https://github.com/alibaba/nacos/releases/download/2.0.4/nacos-server-2.0.4.zip启动进入bin目录,双击startup.cmd进行启动。端口号8848可访问 :http://localhost:8848/nacos/index.html 地址,默认账号密码都是nacos六、注册中心功能1. 服务提供者1新建模块nacos-provider8000pom<dependencies> <!-- spring boot web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud Alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>application.ymlserver: port: 8000 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848创建启动类@SpringBootApplication @EnableDiscoveryClient public class NacosProvider8000Application { public static void main(String[] args) { SpringApplication.run(NacosProvider8000Application.class, args); } }controller@RestController @RequestMapping("/goods") public class GoodsController { @Value("${server.port}") Integer port; @GetMapping("/findById/{id}") public String findById(@PathVariable("id")Integer id){ //业务逻辑 return "nacos provider.port:"+port+"|id:"+id; } }启动项目2. 服务提供者2新建模块nacos-provider8001pom<dependencies> <!-- spring boot web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud Alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>application.ymlserver: port: 8001 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848创建启动类@SpringBootApplication @EnableDiscoveryClient public class NacosProvider8001Application { public static void main(String[] args) { SpringApplication.run(NacosProvider8001Application.class, args); } }controller@RestController @RequestMapping("/goods") public class GoodsController { @Value("${server.port}") Integer port; @GetMapping("/findById/{id}") public String findById(@PathVariable("id")Integer id){ //业务逻辑 return "nacos provider.port:"+port+"|id:"+id; } }启动项目3. 服务消费者新建模块nacos-conusmer9000pom<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud Alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>3.1.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>application.ymlserver: port: 9000 spring: application: name: nacos-consumer cloud: loadbalancer: ribbon: enabled: false nacos: discovery: server-addr: localhost:8848创建启动类@SpringBootApplication @EnableDiscoveryClient public class NacosConsumer9000Application { public static void main(String[] args) { SpringApplication.run(NacosConsumer9000Application.class, args); } }注册RestTemplate@Configuration public class RestTemplateConfig { @Bean @LoadBalanced //loadbalancer 客户端负载均衡 public RestTemplate restTemplate(){ return new RestTemplate(); } }controller@RestController @RequestMapping("/order") public class OrderController { @Autowired private RestTemplate restTemplate; @GetMapping("/add/{id}") public String add(@PathVariable("id")Integer id){ //业务逻辑 String url="http://nacos-provider/goods/findById/"+id; String result = restTemplate.getForObject(url, String.class); return result; } }测试访问:[http://localhost:9000/order/add/1](http://localhost:9000/order/add/1) 4、整合feign引入依赖<!-- openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>主启动类上添加注解@EnableFeignClients新建feign接口@FeignClient("nacos-provider") public interface GoodsFeign { @GetMapping("/goods/findById/{id}") public String findById(@PathVariable("id")Integer id); }controller @Autowired GoodsFeign goodsFeign; @GetMapping("/add2/{id}") public String add2(@PathVariable("id")Integer id){ //feign String str = goodsFeign.findById(id); return str; }测试访问:http://localhost:9000/order/add2/1七、服务注册中心对比1. Nacos 生态图2. Nacos和CAP3. 对比其他注册中心A:可用性 C:一致性 P:分区容错性Nacos默认AP。切换CP:A:可用性C:一致性P:分区容错性Nacos默认AP。切换CP:curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'八、配置中心功能1.创建工程创建新模块nacos-client7777引入依赖 <dependencies> <!-- spring boot web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--SpringCloud Alibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies>添加配置类application.ymlspring: profiles: active: dev #表示开发环境`bootstrap.yml` ``` # nacos配置 server: port: 7777 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 #${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ```启动类@SpringBootApplication @EnableDiscoveryClient @RefreshScope //开启刷新功能 public class NacosClient7777Application { public static void main(String[] args) { SpringApplication.run(NacosClient7777Application.class, args); } }controller@RestController @RequestMapping("/config") @RefreshScope // 开启刷新功能 public class ConfigClientController { @Value("${name}") String name; @GetMapping("/name") public String name() { return name; } } 在Nacos中添加配置信息https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html测试更改配置再次访问接口。说明bus的功能也实现了。2. 分类配置问题1:实际开发中,通常一个系统会准备dev/test/prod环境。如何保证环境启动时服务能正确读取nacos上相应环境的配置文件?答案:namespace区分。问题2:一个大型分布式微服务系统有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境。那怎么对微服务配置进行管理呢?答案:用group把不同的微服务划分到同一个分组里面去。Service就是微服务,一个service可以包含多个cluster集群,nacos默认cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。比方说为了容灾,将service微服务分别部署在了北京机房和上海机房,这是就可以给北京机房的service微服务起一个集群名称BJ,给上海的service微服务起一个集群名称SH,还可以尽量让同一个机房的微服务互相调用,以提升效率。dgn方案 dataid方案(就是nacos的文件名)指定spring.profile.active和配置文件的dataID来使不太环境下读取不同的配置 配置空间+配置分组+新建dev和test两个dataid:就是创建-后不同的两个文件名nacos-config-client-dev.yaml、nacos-config-client-test.yaml 通过IDEA里的spring.profile.active属性就能进行多环境下配置文件的读取。Group方案(默认DEFAULT_GROUP)在nacos创建配置文件时,给文件指定分组。 在IDEA中该group内容 实现的功能:当修改开发环境时,只会从同一group中进行切换。spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 group: BJ_GROUPnamespace方案(默认public)这个是不允许删除的,可以创建一个新的命名空间,会自动给创建的命名空间一个流水号。 在nacos新建命名空间,自动出现e79f32ec-974a-4e90-9086-3ae5c64db7e3在IDEA的yml中指定命名空间namespace: e79f32ec-974a-4e90-9086-3ae5c64db7e3pring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #指定yaml格式的配置 group: BJ_GROUP namespace: e79f32ec-974a-4e90-9086-3ae5c64db7e3![在这里插入图片描述](https://img-blog.csdnimg.cn/f672672cddc74cac93f45df580b2cd63.png) 九、集群和持久化配置第三章 SpringCloud Alibaba Sentinel实现熔断与限流一、简介一句话: 就是hystrix的替代!官网:https://github.com/alibaba/sentinel中文文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。Sentinel 具有以下特征:丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。Sentinel 的主要特性:Sentinel 的开源生态:Sentinel 分为两个部分:核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。二、下载下载地址:https://github.com/alibaba/Sentinel/releases运行:java -jar sentinel-dashboard-1.8.6.jarhttp://localhost:8080账号和密码都是sentinel三、初始化演示工程新建模块cloudalibaba-sentinel-service8000pom <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>ymlserver: port: 8000 spring: application: name: cloudalibaba-sentinal-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 port: 8719主启动类@SpringBootApplication @EnableDiscoveryClient public class CloudalibabaSentinelService8000Application { public static void main(String[] args) { SpringApplication.run(CloudalibabaSentinelService8000Application.class, args); } }controller@RestController public class FlowLimitController { @GetMapping("/testA") public String testA() { return "----testA"; } @GetMapping("/testB") public String testB() { return "----testB"; } }测试启动8050,然后刷新sentinel后台页面(因为sentinel采用懒加载策略,所以需要调用服务后才在后台显示) 在浏览器分别输入,然后刷新sentinel后台页面: http://localhost:8000/demo/testA http://localhost:8000/demo/testB四、熔断降级官方文档:https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A71. 概述除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。注意: 本文档针对 Sentinel 1.8.0 及以上版本。1.8.0 版本对熔断降级特性进行了全新的改进升级,请使用最新版本以更好地利用熔断降级的能力。2. 熔断策略Sentinel 提供以下几种熔断策略:慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。测试:代码 @GetMapping("/testC/{id}") public String testC(@PathVariable("id")Integer id){ if(id==10){ //复杂的业务逻辑 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } return "itlils testC"; }设置策略一直访问10过一会儿,访问10异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。测试:代码@GetMapping("/testD/{id}") public String testD(@PathVariable("id")Integer id){ if(id==10){ //异常调用 int a=1/0; } return "itlils testD"; }设置策略测试疯狂访问过2秒后异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。测试:代码 @GetMapping("/testE/{id}") public String testE(@PathVariable("id")Integer id){ if(id==10){ //异常调用 int a=1/0; } return "itlils testE"; }设置策略疯狂测试 http://localhost:8000/demo/testE/10过几秒 测试http://localhost:8000/demo/testE/1五、热点参数限流官方文档:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%811. 是什么?何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。2. 基本使用引入依赖 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>1.8.4</version> </dependency>controller @GetMapping("/order") @SentinelResource(value = "hotKeys") public String order(@RequestParam("goodsId")String goodsId,@RequestParam("userId")String userId){ //业务逻辑 return "用户下单成功"; }设置规则快速访问http://localhost:8000/demo/order?goodsId=1&userId=10快速访问http://localhost:8000/demo/order?userId=10说明设置限制源代码第一个参数,成功了。为了给调用方一个正确的值,改造@GetMapping("/order") @SentinelResource(value = "hotKeys",blockHandler = "block_order") public String order(@RequestParam(value = "goodsId",required = false)String goodsId ,@RequestParam(value = "userId",required = false)String userId){ //业务逻辑 return "用户下单成功"; } public String block_order(@RequestParam(value = "goodsId",required = false)String goodsId ,@RequestParam(value = "userId",required = false)String userId, BlockException ex){ //记录错误日志 //logger.error(ex.getMessage())) return "用户下单失败,请稍后重试"; }3. 例外情况秒杀情况下,某个参数goodsId=100,最新手机id,单独设置阈值1000。配置疯狂访问http://localhost:8000/demo/order?goodsId=100&userId=10 不会降级注意:参数例外项,仅支持基本类型和字符串类型注意: 加一个:int a=1/0; 业务上的错,sentinel不会管热点key限制,sentinel才会管,疯狂访问六、降级1. 解释熔断:微服务自己限流,不可用了。降级:调用方,提供方错了,返回给客户一个友好对象。2. 新建3个项目cloudalibaba-sentinal-provider9001pom<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>ymlserver: port: 9001 spring: application: name: cloudalibaba-sentinal-provider cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的端口 port: 8719 web-context-unify: false #关闭收敛URL启动类@EnableDiscoveryClientcontroller@RestController @RequestMapping("/goods") public class GoodsController { @Value("${server.port}") Integer port; @GetMapping("/findById/{id}") public Goods findById(@PathVariable("id") Integer id){ //servive-->dao/mapper Goods goods=new Goods(); goods.setGoodId(id); goods.setPrice(123); goods.setTitle("手机.port:"+port); goods.setStock(10); return goods; } } domainpublic class Goods implements Serializable { private Integer goodId; private String title; private double price; private Integer stock; public Goods() { } public Goods(Integer goodId, String title, double price, Integer stock) { this.goodId = goodId; this.title = title; this.price = price; this.stock = stock; } @Override public String toString() { return "Goods{" + "goodId=" + goodId + ", title='" + title + '\'' + ", price=" + price + ", stock=" + stock + '}'; } public Integer getGoodId() { return goodId; } public void setGoodId(Integer goodId) { this.goodId = goodId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Integer getStock() { return stock; } public void setStock(Integer stock) { this.stock = stock; } } 测试cloudalibaba-sentinal-provider9002同理。创建。cloudalibaba-consumer-nacos-consumer8000pom <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>3.1.1</version> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>ymlserver: port: 8000 spring: application: name: cloudalibaba-sentinal-consumer cloud: nacos: discovery: server-addr: localhost:8848 #nacos sentinel: transport: dashboard: localhost:8080 #sentinel port: 8719 #激活Sentinel对Feign的支持 feign: sentinel: enabled: true配置类@Configuration public class RestConfig { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }controller@RestController @RequestMapping("/order") public class OrderController { @Autowired RestTemplate restTemplate; @GetMapping("/add/{id}") public Goods add(@PathVariable("id")Integer id){ String url="http://cloudalibaba-sentinal-provider/goods/findById/"+id; Goods goods = restTemplate.getForObject(url, Goods.class); return goods; } }实体类public class Goods implements Serializable { private Integer goodId; private String title; private double price; private Integer stock; public Goods() { } public Goods(Integer goodId, String title, double price, Integer stock) { this.goodId = goodId; this.title = title; this.price = price; this.stock = stock; } @Override public String toString() { return "Goods{" + "goodId=" + goodId + ", title='" + title + '\'' + ", price=" + price + ", stock=" + stock + '}'; } public Integer getGoodId() { return goodId; } public void setGoodId(Integer goodId) { this.goodId = goodId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Integer getStock() { return stock; } public void setStock(Integer stock) { this.stock = stock; } }测试出现负载均衡3. 不同情况下 blockHandler与fallback情况不配置 if(id<0){ throw new IllegalArgumentException("非法参数"); }else if(id>100){ throw new NullPointerException("查无此商品"); } 只配blockHandlerpublic Goods fail_add(@PathVariable("id")Integer id, BlockException ex){ Goods goods =new Goods(); goods.setGoodId(-1); goods.setPrice(-1); goods.setStock(-1); goods.setTitle("限流之后的特殊对象"); return go正常访问错id疯狂访问只配fallback@SentinelResource(value = "add",fallback = "fallback_add") //业务上的错的话 public Goods fallback_add(@PathVariable("id")Integer id, Throwable ex){ Goods goods =new Goods(); goods.setGoodId(-2); goods.setPrice(-2); goods.setStock(-2); goods.setTitle("业务出错之后的特殊对象"); return goods; }都配置@SentinelResource(value = "add",blockHandler = "fail_add",fallback = "fallback_add")-1 慢慢访问-1 疯狂访问忽略某些异常 @SentinelResource(value = "add",blockHandler = "fail_add",fallback = "fallback_add", exceptionsToIgnore = {IllegalArgumentException.class})**七、feign调用pom<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>yml#前面也已经添加了 #激活Sentinel对Feign的支持 feign: sentinel: enabled: true主启动类@EnableFeignClientsfeign接口@FeignClient(value = "cloudalibaba-sentinal-provider",fallback = GoodsFeignImpl.class) public interface GoodsFeign { @GetMapping("/goods/findById/{id}") public Goods findById(@PathVariable("id") Integer id); }实现类PaymentFallbackService@Component public class GoodsFeignImpl implements GoodsFeign{ @Override public Goods findById(Integer id) { Goods goods =new Goods(); goods.setGoodId(-3); goods.setPrice(-3); goods.setStock(-3); goods.setTitle("feign出错之后的特殊对象"); return goods; } }controller@Autowired GoodsFeign goodsFeign; @GetMapping("/add1/{id}") public Goods add1(@PathVariable("id")Integer id){ Goods goods = goodsFeign.findById(id); return goods; }测试出错,直接停了provider八、配置持久化sentinel的流控配置是临时的,所以我们可以把配置持久化到nacos。pom<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>yml sentinel: datasource: ds1: nacos: server-addr: localhost:8848 #nacos dataId: ${spring.application.name} groupId: DEFAULT_GROUP data-type: json rule-type: flownacos添加配置[ { "resource": "/order/add1/{id}", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]resource:资源名称;limitApp:来源应用;grade:阈值类型,0表示线程数,1表示QPS;count:单机阈值;strategy:流控模式,0表示直接,1表示关联,2表示链路;controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;clusterMode:是否集群测试创作不易,如果有帮助到你,请给文章==点个赞和收藏==,让更多的人看到!!!==关注博主==不迷路,内容持续更新中。
文章
存储  ·  SpringCloudAlibaba  ·  Dubbo  ·  Java  ·  应用服务中间件  ·  调度  ·  Nacos  ·  Sentinel  ·  微服务  ·  Spring
2023-02-25
怎么从零编写一个 v3 版本的 chrome 浏览器插件实现 CSDN 博客网站的暗黑和明亮主题切换?
整体效果流沙插件主题切换演示:https://live.csdn.net/v/228888源码https://github.com/kaimo313/quicksand实现步骤1、新建 manifest.json 文件新建一个 chrome 文件夹,在文件夹里新建 manifest.json 文件,在文件里输入下面代码,给插件取名叫流沙,版本是 1.0。{ "name": "流沙", "version": "1.0", "manifest_version": 3 } 这里需要注意的是 manifest_version 必须是整数 2 或者 3,我们这里使用 3。    2023 年 1 月后 MV2 插件不能再继续更新,MV2 插件将不能在 Chrome 中运行;2023 年 6 月后 即使使用企业策略,MV2 扩展程序也不再在 Chrome 中运行。目前已经不能再发布 MV2 版本的插件,相比于 MV2,MV3 有诸多不同,例如权限控制,API 的变动,发起请求的方式等。2、将插件添加到扩展程序中打开 chrome 浏览器,输入 chrome://extensions/,点击加载已解压的扩展程序,选择刚刚新建的 chrome 文件夹即可。选择好文件夹之后,流沙这个插件就被加载到浏览器中了。3、配置 service-worker.jsbackground script 是扩展的事件处理程序;它包含对扩展很重要的浏览器事件的侦听器。它处于休眠状态,直到触发事件,然后执行指示的逻辑。有效的后台脚本仅在需要时加载,并在空闲时卸载。先在 manifest.json 配置:{ "name": "流沙", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "service-worker.js" } } 跟 manifest.json 同级新建 service-worker.js 文件,里面添加下面代码console.log("流沙---> service-worker", chrome); chrome.action.onClicked.addListener(function () { console.log('点击了流沙插件图标'); }); 然后我们刷新扩展程序页面,发现有个错误错误如下:Uncaught TypeError: Cannot read properties of undefined (reading 'onClicked'),没有onClicked方法chrome.action 文档:必须在 manifest 中声明才能使用此 api在 manifest 中配置 action{ "name": "流沙", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "service-worker.js" }, "action": {} } 刷新清除掉错误之后,点击 Service Worker,就可以看到打印的日志点击右上角的流沙插件,就会打印日志出来。如果没有这个图标的可以添加上来。4、实现点击插件图标切换主题 iconmanifest.json 配置{ "name": "流沙", "version": "1.0", "manifest_version": 3, "description": "用于 CSDN 博客网站的暗黑和明亮主题切换", "author": "kaimo", "background": { "service_worker": "service-worker.js" }, "icons": { "16": "icons/logo.png", "48": "icons/logo.png", "128": "icons/logo.png" }, "action": { "default_icon": { "32": "icons/popup_light_32.png" }, "default_title": "流沙:明亮模式" } } service-worker.js 添加代码console.log("流沙---> service-worker", chrome); let themeType = "light"; chrome.action.onClicked.addListener(function () { console.log('点击了流沙插件图标'); // 修改初始值 themeType = themeType === "light" ? "dark" : "light"; chrome.action.setIcon({ path: "icons/popup_" + themeType + "_32.png" }); chrome.action.setTitle({ title: themeType === "light" ? "流沙:明亮模式" : "流沙:暗黑模式" }); }); 添加 icons 文件夹跟图片效果如下:流沙:明亮模式点击切换到流沙:暗黑模式5、让 CSDN 的网页加载插件先匹配 csdn 网站,在 manifest.json 里添加 content_scripts 的配置"content_scripts": [ { "matches": ["https://*.blog.csdn.net/*"], "js": ["content-script.js"] } ] 然后新建 content-script.js 文件,添加代码alert("匹配到了 CSDN 博客网站")测试效果,访问 csdn 博客网站的时候,就会弹出。其他网址就不会。6、插件里实现样式主题的切换我们发现 id 为 userSkin 的 dom 元素的类不同可以显示不同的主题的效果。暗黑主题效果:skin-blackwhale user-skin-Black明亮主题效果:skin-yellow user-skin-White具体代码如下:manifest.json 文件{ "name": "流沙", "version": "1.0", "manifest_version": 3, "description": "用于 CSDN 博客网站的暗黑和明亮主题切换", "author": "kaimo", "background": { "service_worker": "service-worker.js" }, "icons": { "16": "icons/logo.png", "48": "icons/logo.png", "128": "icons/logo.png" }, "action": { "default_icon": { "32": "icons/popup_light_32.png" }, "default_title": "流沙:明亮模式" }, "content_scripts": [ { "matches": ["https://*.blog.csdn.net/*"], "js": ["content-script.js"] } ], "permissions": ["storage", "tabs"] } service-worker.js 文件console.log("流沙---> service-worker", chrome); // 设置主题类型 function setThemeType(type) { chrome.storage.local.set({ theme: type }, () => { console.log('设置主题模式为:', type); }); } // 通过 tabs 发送消息改变主题类型 // tabs api,必须被注册在 manifest 的 permissions 字段中给插件使用,这里不然获取不到 url。 function changeThemeByTabs(themeType){ chrome.tabs.query({}, tabs => { console.log("获取 tabs", tabs); for (var i = 0; i < tabs.length; i++) { console.log(`tabs[${i}].url`, tabs[i].url); try { const location = new URL(tabs[i].url); const host = location.host; console.log(host, host.includes("blog.csdn.net")); if (host.includes("blog.csdn.net")) { console.log(tabs[i].id, tabs[i], themeType); // 向选项卡发送消息 chrome.tabs.sendMessage(tabs[i].id, { theme: themeType }, response => { // 将打印出"接收到主题切换"; console.log(response); }); } } catch (e) { console.error("报错--->", e); } } }); } // 添加插件监听被安装事件 // 在 onInstalled 监听器内部,扩展使用 storage API 设置一个值。这将允许多个扩展组件访问该值并进行更新。 // 大部分 API,包括 storage api,必须被注册在 manifest 的 permissions 字段中给插件使用。 chrome.runtime.onInstalled.addListener(() => { console.log("插件已安装"); // 设置主题类型 setThemeType("light"); }); // 添加图标点击事件监听 chrome.action.onClicked.addListener(() => { console.log('1、点击了流沙插件图标'); // 获取主题类型 chrome.storage.local.get(["theme"], res => { console.log("2、缓存的theme", res); let { theme } = res; // 修改初始值 theme = theme === "light" ? "dark" : "light"; console.log("3、切换 theme 为:", theme); // 设置图标 chrome.action.setIcon({ path: "icons/popup_" + theme + "_32.png" }); // 设置title chrome.action.setTitle({ title: theme === "light" ? "流沙:明亮模式" : "流沙:暗黑模式" }); // 设置主题类型 setThemeType(theme); // 通过 tabs 发送消息改变主题类型 changeThemeByTabs(theme); }); }); content-script.js 文件console.log("流沙---> content-script", chrome); // 设置明亮主题 function setLightThemes() { document.getElementById("userSkin").className = "skin-yellow user-skin-White"; } // 设置暗黑主题 function setDarkThemes() { document.getElementById("userSkin").className = "skin-blackwhale user-skin-Black"; } // 切换主题 function switchThemes(type = "light") { if(type === "dark") { setDarkThemes(); } else { setLightThemes(); } } // 初始化设置 chrome.storage.local.get(['theme'], res => { let { theme } = res; console.log("初始化设置 theme--->", theme); switchThemes(theme); }); // 监听消息 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log("监听消息--->", request, sender, sendResponse); // 接收信息返回给发送方 sendResponse("接收到主题切换"); const { theme } = request; switchThemes(theme); }); 说明目前这里只实现了博客首页的部分内容样式,感兴趣的可以自己研究完善。另外就是打包扩展程序 crx 文件的生成。第一次可以不用选私有秘钥 .pem 文件。浏览器会自动生成的确定就会生成,不过这个 crx 不能直接放到扩展程序里使用。该扩展程序未列在 Chrome 应用商店中,并可能是在您不知情的情况下添加的。参考 manifest.json 所有配置项官网中给出所有配置项: { // Required - 通俗易懂 "manifest_version": 3, "name": "My Extension", "version": "versionString", // 『重点』action配置项主要用于点击图标弹出框,对于弹出框接受的是html文件 "action": { "default_title": "Click to view a popup", "default_popup": "popup.html" } // 通俗易懂 "default_locale": "en", "description": "A plain text description", "icons": {...}, "author": ..., // 『重点』下面将出现的background.js 配置service work "background": { // Required "service_worker": "service-worker.js", }, // 『重点』下面将出现content_script.js 应用于所有页面上下文的js "content_scripts": [ { "matches": ["https://*.nytimes.com/*"], "css": ["my-styles.css"], "js": ["content-script.js"] } ], // 使用/添加devtools中的功能 "devtools_page": "devtools.html", /** * 三个permission * host_permissions - 允许使用扩展的域名 * permissions - 包含已知字符串列表中的项目 【只需一次弹框要求允许】 * optional_permissions - 与常规类似permissions,但由扩展的用户在运行时授予,而不是提前授予【安全】 * 列出常见选项 * { * activeTab: 当扩展卡选项被改变需要重新获取新的权限 * tabs: 操作选项卡api(改变位置等) * downloads: 访问chrome.downloads API 的权限 便于下载但还是会受到跨域影响 * history: history api权限 * storage: 访问localstorage/sessionStorage权限 * } */ "host_permissions": ["http://*/*", "https://*/*"], "permissions": ["tabs"], "optional_permissions": ["downloads"], // 内部弹出可选页面 - 见fehelper操作页 "options_page": "options.html", "options_ui": { "chrome_style": true, "page": "options.html" }, }
文章
Web App开发  ·  JavaScript  ·  API  ·  数据安全/隐私保护
2023-02-24
前端重点:HTTP协议
原文来自我的个人博客1. HTTP 基础1.1 HTTP 协议是什么?HTTP (HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范在计算机和网络世界有,存在不同的协议,如广播协议、寻址协议、路由协议等等......而 HTTP 是一个传输协议,即将数据由 A 传到 B 或将 B 传输到 A ,并且 A 与 B 之间能够存放很多第三方,如: A<=>X<=>Y<=>Z<=>B传输的数据并不是计算机底层中的二进制包,而是完整的、有意义的数据,如 HTML 文件, 图片文件, 查询结果等超文本,能够被上层应用识别在实际应用中,HTTP 常被用于在 Web浏览器 和 网站服务器 之间传递信息,以明文方式发送内容,不提供任何方式的数据加密1.2 HTTP 协议的特点应用层协议(下面可以是 TCP/IP)信息纯文本传输支持客户/服务器模式简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单,使得HTTP 服务器的程序规模小,因而通信速度很快灵活:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记无状态:每次请求独立,请求之间互相不影响(浏览器提供了手段维护状态比如Cookie, Session, Storage 等)1.3 HTTP 协议的历史1991 HTTP 0.9 (实验版本)1996 HTTP 1.0 (有广泛用户)1999 HTTP 1.1 (影响面最大的版本)2015 HTTP 2.0 (大公司基本上都是2.0了)2018 HTTP 3.0 (2022 年 6 月 6 日正式发布了)1.4 Header 和 BodyHTTP 是一个文本传输协议,传输内容是人类可读的文本,大体分成两部分:请求头(Header)/ 返回头消息体(Body)下面演示一个 Node.js 实战 http 请求const net = require("net"); const response = ` HTTP/1.1 200 OK Data: Tue,30 Jun 2022 01:00:00 GMT Content-Type: text/plain Connection: Closed Hello World `; const server = net.createServer((socket) => { socket.end(response); }); server.listen(80, () => { console.log("80端口启动"); }); 从浏览器中观察1.5. HTTPS在上述介绍 HTTP 中,了解到 HTTP 传递信息是以明文的形式发送内容,这并不安全。而 HTTPS 出现正是为了解决 HTTP 不安全的特性为了保证这些隐私数据能加密传输,让 HTTP 运行安全的 SSL/TLS 协议上,即 HTTPS = HTTP + SSL/TLS,通过 SSL 证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密SSL 协议位于 TCP/IP 协议与各种应用层协议之间,浏览器和服务器在使用 SSL 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持流程图如下:1.6. HTTP 和 HTTPS 的区别HTTPS 是 HTTP 协议的安全版本,HTTP 协议的数据传输是明文的,是不安全的,HTTPS 使用了 SSL/TLS 协议进行了加密处理,相对更安全HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP 是 80,HTTPS 是 443HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTPHTTPS需要 SSL,SSL 证书需要钱,功能越强大的证书费用越高2. HTTP 详情2.1. HTTP 常见请求头2.1.1 请求头是什么?HTTP 头字段(HTTP header fields),是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分它们定义了一个超文本传输协议事务中的操作参数HTTP 头部字段可以自己根据需要定义,因此可能在 Web 服务器和浏览器上发现非标准的头字段下面是一个 HTTP 常见的请求头:下面是一个 HTTP 常见的响应头2.2 常见HTTP头2.2.1 Content-Length发送给接受者的 Body 内容长度(字节)一个 byte 是 8bitUTF-8编码的字符1-4个字节、示例:Content-Length: 3482.2.2 User-Agent帮助区分客户端特性的字符串 - 操作系统 - 浏览器 - 制造商(手机类型等) - 内核类型 - 版本号 示例:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.362.2.3 Content-Type帮助区分资源的媒体类型(Media Type/MIME Type)text/htmltext/cssapplication/jsonimage/jpeg示例:Content-Type: application/x-www-form-urlencoded2.2.4 Origin:描述请求来源地址scheme://host:port不含路径可以是null示例: Origin: https://yewjiwei.com2.2.5 Accept建议服务端返回何种媒体类型(MIME Type)/代表所有类型(默认)多个类型用逗号隔开衍生的还有Accept-Charset能够接受的字符集 示例:Accept-Charset: utf-8Accept-Encoding能够接受的编码方式列表 示例:Accept-Encoding: gzip, deflateAccept-Language能够接受的回应内容的自然语言列表 示例:Accept-Language: en-US示例:Accept: text/plainAccept-Charset: utf-8Accept-Encoding: gzip, deflate2.2.6 Referer告诉服务端打开当前页面的上一张页面的URL;如果是ajax请求那么就告诉服务端发送请求的URL是什么非浏览器环境有时候不发送Referer常常用户行为分析2.2.7 Connection决定连接是否在当前事务完成后关闭HTTP1.0默认是closeHTTP1.1后默认是keep-alive2.2.8 Authorization用于超文本传输协议的认证的认证信息示例: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==2.2.9 Cache-Control用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令示例: Cache-Control: no-cache2.2.10 Date发送该消息的日期和时间示例: Date: Tue, 15 Nov 1994 08:12:31 GMT2.3. 基本方法GET 从服务器获取资源(一个网址url代表一个资源)POST 在服务器创建资源PUT 在服务器修改资源DELETE 在服务器删除资源OPTIONS 跟跨域相关TRACE 用于显示调试信息CONNECT 代理PATCH 对资源进行部分更新(极少用)2.4. 状态码1XX: 提供信息100 continue 情景:客户端向服务端传很大的数据,这个时候询问服务端,如果服务端返回100,客户端就继续传 (历史,现在比较少了)101 协议切换switch protocolHTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade告诉客户端把协议切换为Websocket2xx: 成功200 Ok 正常的返回成功 通常用在GET201 Created 已创建 通常用在POST202 Accepted 已接收 比如发送一个创建POST请求,服务端有些异步的操作不能马上处理先返回202,结果需要等通知或者客户端轮询获取203 Non-Authoritative Infomation 非权威内容 原始服务器的内容被修改过204 No Content 没有内容 一般PUT请求修改了但是没有返回内容205 Reset Content 重置内容206 Partial Content 服务端下发了部分内容3XX: 重定向300 Multiple Choices 用户请求了多个选项的资源(返回选项列表)301 Moved Permanently 永久转移302 Found 资源被找到(以前是临时转移)不推荐用了 302拆成了303和307303 See Other 可以使用GET方法在另一个URL找到资源304 Not Modified 没有修改305 Use Proxy 需要代理307 Temporary Redirect 临时重定向 (和303的区别是,307使用原请求的method重定向资源, 303使用GET方法重定向资源)308 Permanent Redirect 永久重定向 (和301区别是 客户端接收到308后,之前是什么method,之后也会沿用这个method到新地址。301,通常给用户会向新地址发送GET请求)4XX: 客户端错误400 Bad Request 请求格式错误401 Unauthorized 没有授权402 Payment Required 请先付费403 Forbidden 禁止访问404 Not Found 没有找到405 Method Not Allowed 方法不允许406 Not Acceptable 服务端可以提供的内容和客户端期待的不一样5XX: 服务端错误500 Internal Server Error 内部服务器错误501 Not Implemented 没有实现502 Bad Gateway 网关错误503 Service Unavailable 服务不可用 (内存用光了,线程池溢出,服务正在启动)504 Gateway Timeout 网关超时505 HTTP Version Not Supported 版本不支持3. TCP vs UDP3.1. TCPTCP(Transmission Control Protocol),传输控制协议,是一种可靠、面向字节流的通信协议,把上面应用层交下来的数据看成无结构的字节流来发送。可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小。TCP 报文首部有 20 个字节,额外开销大。特点如下:TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)3.2 UDPUDP(User Datagram Protocol),用户数据包协议,是一个简单的面向数据报的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个报文而对接收方,接到后直接去除首部,交给上面的应用层就完成任务UDP报头包括 4 个字段,每个字段占用 2 个字节(即 16 个二进制位),标题短,开销小特点如下:UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务传输途中出现丢包,UDP 也不负责重发当包的到达顺序出现乱序时,UDP没有纠正的功能。并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为3.3 区别UDP 与 TCP 两者的都位于传输层,如下图所示:两者区别如下表所示:标题TCPUDP可靠性可靠不可靠连接性面向连接无连接报文面向字节流面向报文效率传输效率低传输效率高双共性全双工一对一、一对多、多对一、多对多流量控制滑动窗口无拥塞控制慢开始、拥塞避免、快重传、快恢复无传输效率慢快TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,UDP是面向无连接,数据传输前后不连接连接,发送端只负责将数据发送到网络,接收端从消息队列读取TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。UDP 则尽可能传递数据,但不保证传递交付给对方TCP 面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配。UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信两者应用场景如下图:可以看到,TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景4. HTTP 1.0 / 1.1 / 2.0 / 3.04.1. HTTP1.0HTTP协议的第二个版本,第一个在通讯中指定版本号的HTTP协议版本HTTP 1.0 浏览器与服务器只保持短暂的连接,每次请求都需要与服务器建立一个TCP连接服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求简单来讲,每次与服务器交互,都需要新开一个连接例如,解析html文件,当发现文件中存在资源文件的时候,这时候又创建单独的链接最终导致,一个html文件的访问包含了多次的请求和响应,每次请求都需要创建连接、关系连接这种形式明显造成了性能上的缺陷如果需要建立长连接,需要设置一个非标准的Connection字段 Connection: keep-alive4.2. HTTP1.1在HTTP1.1中,默认支持长连接(Connection: keep-alive),即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟建立一次连接,多次请求均由这个连接完成这样,在加载html文件的时候,文件中多个请求和响应就可以在一个连接中传输同时,HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间同时,HTTP1.1在HTTP1.0的基础上,增加更多的请求头和响应头来完善的功能,如下:引入了更多的缓存控制策略,如If-Unmodified-Since, If-Match, If-None-Match等缓存头来控制缓存策略引入range,允许值请求资源某个部分引入host,实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点并且还添加了其他的请求方法:put、delete、options...4.3. HTTP2.0HTTP2.0在相比之前版本,性能上有很大的提升,添加了如下特性多路复用二进制分帧首部压缩服务器推送4.3.1 多路复用HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”上图中,可以看到第四步中css、js资源是同时发送到服务端4.3.2 二进制分帧帧是HTTP2通信中最小单位信息HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x的文本格式,解析起来更高效将请求和响应数据分割为更小的帧,并且它们采用二进制编码HTTP2中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件4.3.3 首部压缩HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新例如:下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销4.3.4 服务器推送HTTP2引入服务器推送,允许服务端推送资源给客户端服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以随同页面的其它资源免得客户端再次创建连接发送请求到服务器端获取这种方式非常合适加载静态资源4.4. HTTP3.0我们都知道,HTTP是应用层协议,应用层产生的数据会通过传输层协议作为载体来传输到互联网上的其他主机中,而其中的载体就是 TCP 协议,这是 HTTP 2 之前的主流模式。但是随着 TCP 协议的缺点不断暴露出来, HTTP 3.0 毅然决然切断了和 TCP 的联系,转而拥抱了 UDP 协议,这么说不太准确,其实 HTTP 3.0 其实是拥抱了 QUIC 协议,而 QUIC 又被叫做快速 UDP 互联网连接,QUIC的一个特征就是快。QUIC 具有下面这些优势使用 UDP 协议,不需要三次连接进行握手,而且也会缩短 TLS 建立连接的时间。解决了队头阻塞问题。实现动态可插拔,在应用层实现了拥塞控制算法,可以随时切换。报文头和报文体分别进行认证和加密处理,保障安全性。连接能够平滑迁移。4.5. 总结HTTP 1.0:浏览器与服务器只保持短连接,浏览器的每次请求都需要与服务器建立一个TCP连接。HTTP 1.1:引入了长连接,即TCP连接默认不关闭,可以被多个请求复用引入pipelining管道技术,在同一个TCP连接里面,客户端可以同时发送多个请求,但有队头阻塞问题。新增了一些请求方法新增了一些请求头和响应头HTTP 2.0:采用二进制格式而非文本格式多路复用TCP连接,在一个链接上客户端或服务器可同时发送多个请求或响应,避免了队头阻塞问题(只解决粒度级别为http request的队头阻塞)使用报头压缩,降低开销服务器推送HTTP 3.0:利用QUIC作为底层支撑协议,其融合UDP协议的速度、性能与TCP的安全可靠。彻底解决了队头阻塞问题连接建立更快支持连接迁移
文章
Web App开发  ·  缓存  ·  网络协议  ·  安全  ·  前端开发  ·  JavaScript  ·  网络安全  ·  网络性能优化  ·  数据安全/隐私保护  ·  Windows
2023-02-26
...
跳转至:
开发与运维
5786 人关注 | 133444 讨论 | 319487 内容
+ 订阅
  • office全版本软件安装包(win+mac版本)——2016office软件下载
  • 软件测试|测试金字塔是什么,它的目的是什么,以及它包含哪些层次?
  • 软件测试|SQL分类大概有几种?SQL中什么是主键和外键,它们之间的区别是什么?
查看更多 >
安全
1247 人关注 | 24148 讨论 | 85887 内容
+ 订阅
  • office软件2016版本下载安装教程——office全版本软件安装包
  • S/MIME电子邮件证书,符合FDA邮件安全要求
  • 如何申请iOS推送证书p12文件并配置极光推送平台
查看更多 >
人工智能
2875 人关注 | 12395 讨论 | 102662 内容
+ 订阅
  • office全版本软件安装包(win+mac版本)——2016office软件下载
  • 超火的AI绘画Midjourney 超详细教程来了。含关键词及账号
  • 新能源的B面:下沉市场的机会与变量
查看更多 >
云原生
234326 人关注 | 11613 讨论 | 47404 内容
+ 订阅
  • 软件测试|测试金字塔是什么,它的目的是什么,以及它包含哪些层次?
  • k8s StorageClass详解
  • 靠近用户侧和数据,算网融合实现极致协同
查看更多 >
数据库
252946 人关注 | 52318 讨论 | 99250 内容
+ 订阅
  • 软件测试|SQL分类大概有几种?SQL中什么是主键和外键,它们之间的区别是什么?
  • Navicat软件介绍以及使用教程
  • 如何当个优秀的文档工程师?从 TC China 看技术文档工程师的自我修养
查看更多 >