无法访问netflix服务_Choerodon 的微服务之路(三):服务注册与发现

本文是 Choerodon 的微服务之路系列推文第三篇。在上一篇《Choerodon的微服务之路(二):微服务网关》中,介绍了Choerodon 在搭建微服务网关时考虑的一些问题以及两种常见的微服务网关模式,并且通过代码介绍了Choerodon 的网关是如何实现的。本篇文章将介绍Choerodon 的注册中心,通过代码的形式介绍 Choerodon 微服务框架中,是如何来实现服务注册和发现的。

▌文章的主要内容包括:

  • 服务注册/发现
  • 服务注册表
  • 健康检查

在上一篇文章的开始,我们提到解决微服务架构中的通信问题,基本只要解决下面三个问题:

  • 服务网络通信能力
  • 服务间的数据交互格式
  • 服务间如何相互发现与调用

网络的互通保证了服务之间是可以通信的,通过对JSON 的序列化和反序列化来实现网络请求中的数据交互。Choerodon 的 API 网关则统一了所有来自客户端的请求,并将请求路由到具体的后端服务上。然而这里就会有一个疑问,API 网关是如何与后端服务保持通信的,后端服务之间又是如何来进行通信的?当然我们能想到最简单的方式就是通过 URL + 端口的形式直接访问(例如:http://127.0.0.1:8080/v1/hello)。

在实际的生产中,我们认为这种方式应该是被避免的。因为 Choerodon 的每个服务实例都部署在 K8S 的不同 pod 中,每一个服务实例的 IP 地址和端口都可以改变。同时服务间相互调用的接口地址如何管理,服务本身集群化后又是如何进行负载均衡。这些都是我们需要考虑的。

为了解决这个问题,自然就想到了微服务架构中的注册中心。一个注册中心应该包含下面几个部分:

  • 服务注册/发现:服务注册是微服务启动时,将自己的信息注册到注册中心的过程。服务发现是注册中心监听所有可用微服务,查询列表及其网络地址。
  • 服务注册表:用来纪录各个微服务的信息。
  • 服务检查:注册中心使用一定的机制定时检测已注册的服务,如果发现某实例长时间无法访问,就会从服务注册表中移除该实例。

Choerodon 中服务注册的过程如下图所示:

dc9df52d2e1a0e9ec7b0ce66ccb88d9e.png

服务注册/发现

当我们通过接口去调用其他服务时,调用方则需要知道对应服务实例的 IP 地址和端口。对于传统的应用而言,服务实例的网络地址是相对不变的,这样可以通过固定的配置文件来读取网络地址,很容易地使用 HTTP/REST 调用另一个服务的接口。

但是在微服务架构中,服务实例的网络地址是动态分配的。而且当服务进行自动扩展,更新等操作时,服务实例的网络地址则会经常变化。这样我们的客户端则需要一套精确地服务发现机制。

Eureka 是 Netflix 开源的服务发现组件,本身是一个基于 REST 的服务。它包含 Server 和 Client 两部分。

Eureka Server 用作服务注册服务器,提供服务发现的能力,当一个服务实例被启动时,会向 Eureka Server 注册自己的信息(例如IP、端口、微服务名称等)。这些信息会被写到注册表上;当服务实例终止时,再从注册表中删除。这个服务实例的注册表通过心跳机制动态刷新。这个过程就是服务注册,当服务实例注册到注册中心以后,也就相当于注册中心发现了服务实例,完成了服务注册/发现的过程。

阅读 Spring Cloud Eureka 的源码可以看到,在 eureka-client-1.6.2.jar 的包中,com.netflix.discovery。 DiscoveryClient 启动的时候,会初始化一个定时任务,定时的把本地的服务配置信息,即需要注册到远端的服务信息自动刷新到注册服务器上。该类包含了 Eureka Client 向 Eureka Server 注册的相关方法。

在 DiscoveryClient 类有一个服务注册的方法 register(),该方法是通过 HTTP 请求向 Eureka Server 注册。其代码如下:

boolean register() throws Throwable {logger.info(PREFIX + appPathIdentifier + ": registering service...");EurekaHttpResponse<Void> httpResponse;try {httpResponse = eurekaTransport.registrationClient.register(instanceInfo);} catch (Exception e) {logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);throw e;}if (logger.isInfoEnabled()) {logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());}return httpResponse.getStatusCode() == 204;}

​对于 Choerodon 而言,客户端依旧采用 Eureka Client,而服务端采用 GoLang 编写,结合 K8S,通过主动监听 K8S 下 pod 的启停,发现服务实例上线,Eureka Client 则通过 HTTP 请求获取注册表,来实现服务注册/发现过程。

注册中心启动时,会构造一个 podController,用来监听pod 的生命周期。代码如下:

func Run(s *options.ServerRunOptions, stopCh <-chan struct{}) error {... ... podController := controller.NewController(kubeClient, kubeInformerFactory, appRepo)go kubeInformerFactory.Start(stopCh)go podController.Run(instance, stopCh, lockSingle)return registerServer.PrepareRun().Run(appRepo, stopCh)
}

​在 http://github.com/choerodon/go-register-server/controller/controller.go 中定义了 Controller,提供了 Run() 方法,该方法会启动两个进程,用来监听环境变量 REGISTER_SERVICE_NAMESPACE 中配置的对应 namespace 中的 pod,然后在 pod 启动时,将 pod 信息转化为自定义的服务注册信息,存储起来。在 pod 下线时,从存储中删除服务信息。其代码如下:

func (c *Controller) syncHandler(key string, instance chan apps.Instance, lockSingle apps.RefArray) (bool, error) {namespace, name, err := cache.SplitMetaNamespaceKey(key)if err != nil {runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))return true, nil}   pod, err := c.podsLister.Pods(namespace).Get(name)if err != nil {if errors.IsNotFound(err) {if ins := c.appRepo.DeleteInstance(key); ins != nil {ins.Status = apps.DOWNif lockSingle[0] > 0 {glog.Info("create down event for ", key)instance <- *ins}}runtime.HandleError(fmt.Errorf("pod '%s' in work queue no longer exists", key))return true, nil}   return false, err}   _, isContainServiceLabel := pod.Labels[ChoerodonServiceLabel]_, isContainVersionLabel := pod.Labels[ChoerodonVersionLabel]_, isContainPortLabel := pod.Labels[ChoerodonPortLabel] if !isContainServiceLabel || !isContainVersionLabel || !isContainPortLabel {return true, nil}   if pod.Status.ContainerStatuses == nil {return true, nil}   if container := pod.Status.ContainerStatuses[0]; container.Ready && container.State.Running != nil && len(pod.Spec.Containers) > 0 {if in := convertor.ConvertPod2Instance(pod); c.appRepo.Register(in, key) {ins := *inins.Status = apps.UPif lockSingle[0] > 0 {glog.Info("create up event for ", key)instance <- ins}}   } else {if ins := c.appRepo.DeleteInstance(key); ins != nil {ins.Status = apps.DOWNif lockSingle[0] > 0 {glog.Info("create down event for ", key)instance <- *ins}}}   return true, nil
}

http://github.com/choerodon/go-register-server/eureka/repository/repository 中的 ApplicationRepository 提供了 Register() 方法,该方法手动将服务的信息作为注册表存储在注册中心中。

func (appRepo *ApplicationRepository) Register(instance *apps.Instance, key string) bool {if _, ok := appRepo.namespaceStore.Load(key); ok {return false} else {appRepo.namespaceStore.Store(key, instance.InstanceId)}appRepo.instanceStore.Store(instance.InstanceId, instance)return true
}

通过上面的代码我们可以了解到Choerodon 注册中心是如何实现服务注册的。有了注册中心后,下面我们来介绍下服务发现中的服务注册表。

服务注册表

在微服务架构中,服务注册表是一个很关键的系统组件。当服务向注册中心的其他服务发出请求时,请求调用方需要获取注册中心的服务实例,知道所有服务实例的请求地址。

Choerodon 沿用 Spring Cloud Eureka 的模式,由注册中心保存服务注册表,同时客户端缓存一份服务注册表,每经过一段时间去注册中心拉取最新的注册表。

在http://github.com/choerodon/go-register-server/eureka/apps/types 中定义了 Instance 对象,声明了一个微服务实例包含的字段。代码如下:

type Instance struct {InstanceId       string            `xml:"instanceId" json:"instanceId"`HostName         string            `xml:"hostName" json:"hostName"`App              string            `xml:"app" json:"app"`IPAddr           string            `xml:"ipAddr" json:"ipAddr"`Status           StatusType        `xml:"status" json:"status"`OverriddenStatus StatusType        `xml:"overriddenstatus" json:"overriddenstatus"`Port             Port              `xml:"port" json:"port"`SecurePort       Port              `xml:"securePort" json:"securePort"`CountryId        uint64            `xml:"countryId" json:"countryId"`DataCenterInfo   DataCenterInfo    `xml:"dataCenterInfo" json:"dataCenterInfo"`LeaseInfo        LeaseInfo         `xml:"leaseInfo" json:"leaseInfo"`Metadata         map[string]string `xml:"metadata" json:"metadata"`HomePageUrl      string            `xml:"homePageUrl" json:"homePageUrl"`StatusPageUrl    string            `xml:"statusPageUrl" json:"statusPageUrl"`HealthCheckUrl   string            `xml:"healthCheckUrl" json:"healthCheckUrl"`VipAddress       string            `xml:"vipAddress" json:"vipAddress"`SecureVipAddress string            `xml:"secureVipAddress" json:"secureVipAddress"`IsCoordinatingDiscoveryServer bool `xml:"isCoordinatingDiscoveryServer" json:"isCoordinatingDiscoveryServer"`    LastUpdatedTimestamp uint64 `xml:"lastUpdatedTimestamp" json:"lastUpdatedTimestamp"`LastDirtyTimestamp   uint64 `xml:"lastDirtyTimestamp"   json:"lastDirtyTimestamp"`ActionType           string `xml:"actionType" json:"actionType"`
}

客户端可以通过访问注册中心的/eureka/apps 接口获取对应的注册表信息。如下所示:

{"name": "iam-service","instance": [{"instanceId": "10.233.73.39:iam-service:8030","hostName": "10.233.73.39","app": "iam-service","ipAddr": "10.233.73.39","status": "UP","overriddenstatus": "UNKNOWN","port": {"@enabled": true,"$": 8030},"securePort": {"@enabled": false,"$": 443},"countryId": 8,"dataCenterInfo": {"name": "MyOwn","@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"},"leaseInfo": {"renewalIntervalInSecs": 10,"durationInSecs": 90,"registrationTimestamp": 1542002980,"lastRenewalTimestamp": 1542002980,"evictionTimestamp": 0,"serviceUpTimestamp": 1542002980},"metadata": {"VERSION": "2018.11.12-113155-master"},"homePageUrl": "http://10.233.73.39:8030/","statusPageUrl": "http://10.233.73.39:8031/info","healthCheckUrl": "http://10.233.73.39:8031/health","vipAddress": "iam-service","secureVipAddress": "iam-service","isCoordinatingDiscoveryServer": true,"lastUpdatedTimestamp": 1542002980,"lastDirtyTimestamp": 1542002980,"actionType": "ADDED"}]
}

我们可以在服务注册表中获取到所有服务的 IP 地址、端口以及服务的其他信息,通过这些信息,服务直接就可以通过 HTTP 来进行访问。有了注册中心和注册表之后,我们的注册中心又是如何来确保服务是健康可用的,则需要通过健康检查机制来实现。

健康检查

在我们提供了注册中心以及服务注册表之后,我们还需要确保我们的服务注册表中的信息,与服务实际的运行状态保持一致,需要提供一种机制来保证服务自身是可被访问的。在Choerodon微服务架构中处理此问题的方法是提供一个健康检查的端点。当我们通过 HTTP 进行访问时,如果能够正常访问,则应该回复 HTTP 状态码200,表示健康。

Spring Boot 提供了默认的健康检查端口。需要添加spring-boot-starter-actuator 依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

​访问 /health 端点后,则会返回如下类似的信息表示服务的状态。可以看到 HealthEndPoint 给我们提供默认的监控结果,包含磁盘检测和数据库检测等其他信息。

{"status": "UP","diskSpace": {"status": "UP","total": 398458875904,"free": 315106918400,"threshold": 10485760},"db": {"status": "UP","database": "MySQL","hello": 1}
}

​但是因为 Choerodon 使用的是 K8S 作为运行环境。我们知道 K8S 提供了 liveness probes 来检查我们的应用程序。而对于 Eureka Client 而言,服务是通过心跳来告知注册中心自己是 UP 还是 DOWN的。这样我们的系统中则会出现两种检查机制,则会出现如下几种情况。

  • K8S 通过,/health 通过
  • K8S 通过,/health 未通过
  • K8S 未通过,/health 通过

第一种情况,当两种都通过的话,服务是可以被访问的。

第二种情况,K8S 认为服务是正常运行的,但注册中心认为服务是不健康的,注册表中不会记录该服务,这样其他服务则不能获取该服务的注册信息,也就不会通过接口进行服务调用。则服务间不能正常访问,如下图所示:

e37fe33b7c01fe16dac41902ce4cff4b.png

第三种情况,服务通过心跳告知注册中心自己是可用的,但是可能因为网络的原因,K8S 将 pod 标识为不可访问,这样当其他服务来请求该服务时,则不可以访问。这种情况下服务间也是不能正常访问的。如下图所示:

d66adce50a1f485d54dd876adb9ddad9.png

同时,当我们配置了管理端口之后,该端点则需要通过管理端口进行访问。可以再配置文件中添加如下配置来修改管理端口。

management.port: 8081

​当我们开启管理端口后,这样会使我们的健康检查变得更加复杂,健康检查并不能获取服务真正的健康状态。

在这种情况下,Choerodon 使用 K8S 来监听服务的健康端口,同时需要保证服务的端口与管理端口都能被正常访问,才算通过健康检查。可以在部署的 deploy 文件中添加 readinessProbe 参数。

apiVersion: v1
kind: Pod
spec:containers:readinessProbe:exec:command:- /bin/sh- -c- curl -s localhost:8081/health --fail && nc -z localhost 8080failureThreshold: 3initialDelaySeconds: 60periodSeconds: 10successThreshold: 1timeoutSeconds: 10

这样,当我们的服务启动之后,才会被注册中心正常的识别。当服务状态异常时,也可以尽快的从注册表中移除。

总结

回顾一下这篇文章,我们介绍了 Choerodon 的注册中心,通过代码的形式介绍了 Choerodon 微服务框架中,是如何来实现服务注册和发现的,其中 Spring Cloud 的版本为 Dalston.SR4。具体的代码可以参见我们的 github 地址(https://github.com/choerodon/go-register-server)。

更多关于微服务系列的文章,点击蓝字可阅读 ▼

Choerodon的微服务之路(二):微服务网关

Choerodon的微服务之路(一):如何迈出关键的第一步

关于Choerodon猪齿鱼

Choerodon猪齿鱼是一个开源企业服务平台,是基于Kubernetes的容器编排和管理能力,整合DevOps工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的开源平台,同时提供IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

  • 猪齿鱼官网:http://choerodon.io
  • 论坛:http://forum.choerodon.io
  • Github:https://github.com/choerodon

由Choerodon猪齿鱼核心团队创立的 BuildRun(https://gobuildrun.com),基于猪齿鱼带来了多云架构环境下基于视觉的企业级应用创建、集成、部署、生命周期管理和分发的能力。

BuildRun 以云原生的现代化软件架构来帮助企业提升软件开发生产力和业务敏捷性,提供多云低代码应用平台和多云应用生命周期管理平台,帮助企业隐藏应用开发和运行时的基础架构复杂性,让每个人专注于业务逻辑,促进团队快速、持续地将想法转化为真正的商业价值。

团队在云原生技术和架构(DevOps、持续交付、微服务和容器)应用场景,如企业数字化转型、企业中台等方面拥有丰富的经验,公司客户包括商业地产、建筑工程、医药、家居、汽车配业、大型工业等企业,为您提供最合适的解决方案。

免费注册试用:https://apps.gobuildrun.com/#/base/register-organization

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

excel中如何取消自动超链接?

最近做的表格有点多&#xff0c;年终述职也到了。总有一些地方生疏了&#xff0c;幸好还有点小印象。记录下来&#xff0c;以后可以回来看看。方法一 适合单个链接的取消 1输入网址后&#xff0c;按回车键确认&#xff0c;快捷键ctrlz&#xff0c;即可取消&#xff0c;这种不好…

大根堆的删除c语言,大根堆和小根堆的C语言实现

大根堆小根堆的实现&#xff1a;以PPT形式呈现大根堆构建的理论过程1、首先涉及到一个堆的调整&#xff0c;这也是算法的核心部分。假设树中&#xff0c;节点i的子树已经为两个大根堆。这两个子树再加上i节点的话&#xff0c;可能是大根堆也可能不是&#xff0c;因此需要对节点…

网页结构 盒模型

HTML是个什么鬼&#xff1f; 前端开发人员要想和浏览器沟通&#xff0c;就要用到浏览器才能够识别的语言&#xff08;HTML超文本标记语言&#xff09;&#xff0c;所以他是一门浏览器能够识别的语言。是一种由标签组成的超文本标记语言&#xff0c;而非编程语言。一个html文档…

idea创建git分支

此时只是在本地创建好了分支&#xff0c;修改源代码后add&#xff0c;commit将本地分支提交到远程仓库分支已创建&#xff0c;其它成员此时就可以从git拉分支转载于:https://www.cnblogs.com/qianqiu-1026/p/8589218.html

已知华氏温度f c语言,编程题:已知两种温度的换算公式C=(5/9)(F-32),试编写一个程序输入华氏度F,输出摄氏度。...

使用python的写法为&#xff1a;valinput("请输入带有温度表示符号的温度值(例如&#xff1a;32c)")if val[-1] in ["C","c"]:f1.8*float(val[0:-1])32print("转换后的温度为&#xff1a;%.2fF"%f)elif val[-1] in ["F",&qu…

golang ffmpeg 做网络直播

最近在公司做在线视频转码的工作&#xff0c;研究了下ffmpeg 最后直接研究了下网络直播&#xff0c;我是在我自己的mac 上面测试的&#xff0c;效果&#xff0c;还可以&#xff0c;先看看效果图吧 ffmpeg 我是通过brew安装 的&#xff0c;这步就略了 VLC这个播放器怎么安装的也…

androidstudio带pom的上传到jcenter_输送机@网带输送机@304网带输送机@304不锈钢网带输送机@输送机网带厂家定制...

输送机网带输送机食品网带输送机304网带输送机304不锈钢网带输送机输送机网带厂家定制输送机主要用于运输食品原料或成品。食品输送机根据输送带不同可分为皮带的&#xff0c;链板的&#xff0c;网带的。输送形式有&#xff1a;直行的&#xff0c;爬坡提升的&#xff0c;清洗的…

box-sizing -- 盒模型

项目开发中&#xff0c;在浏览同事的代码&#xff0c;发现他经常用一个属性--box-sizing&#xff0c;很好奇是什么&#xff0c;于是乎&#xff0c;上网查阅资料学了起来。  首先我们先复习一下盒模型的组成&#xff1a;一个div通常由 content(内容) margin padding border组成…

转载大神的一篇文章----【如何选择开源许可证?】

原文地址&#xff1a;http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html 如何为代码选择开源许可证&#xff0c;这是一个问题。 世界上的开源许可证&#xff0c;大概有上百种。很少有人搞得清楚它们的区别。即使在最流行的六种----GPL、BSD、…

教你如何用 lib-flexible 实现移动端H5页面适配

前话 好久没写教程了&#xff08;可能会误导新手的菜鸟教程(&#xffe3;▽&#xffe3;)"&#xff09;。 这是我的github&#xff0c;欢迎前端大大们和我一起学习交流 https://github.com/pwcong 最近入职公司做前端实习&#xff0c;这几个星期来学到了移动端H5页面适配…

使用GlassFish 3.1.2.2和Primefaces 3.4的JDBC领域和基于表单的身份验证

我的博客上最受欢迎的帖子之一是有关JDBC安全领域和带有Primefaces的GlassFish上基于表单的身份验证的简短教程。 在收到有关它不再适用于最新的GlassFish 3.1.2.2的评论后&#xff0c;我认为可能是时候重新访问它并提出更新的版本了。 开始了&#xff1a; 制备 就像在原始教程…

thinkcmf常用标签

1、图片地址&#xff1a;{:cmf_get_image_url($vo.icon)} 2、模板控件 模板变量调用&#xff1a;$theme_vars.title <widget name"aboutUs">{$widget.title} //控件标题 {$widget.vars.subTitle} //控件变量 subTitle {:nl2br($widget.vars.content)} //输…

ubuntu下docker安装,配置python运行环境

参考自: 1.最详细ubuntu安装docker教程 2.使用docker搭建python环境 首先假设已经安装了docker&#xff0c;卸载原来的docker 在命令行中运行&#xff1a; sudo apt-get updatesudo apt-get remove docker docker-engine docker.io containerd runc 安装docker依赖 apt-get…

python 打造一个sql注入脚本 (一)

0x00前言&#xff1a; 昨天刚刚看完小迪老师的sql注入篇的第一章 所以有了新的笔记。 0x01笔记&#xff1a; sql注入原理&#xff1a; 网站数据传输中&#xff0c;接受变量传递的值未进行过滤&#xff0c;导致直接带入数据库查询执行的操作。 sql注入对渗透的作用&#xff1a; …

如何给VirtualBox虚拟机的ubuntu LVM分区扩容

我在VirtualBox安装的ubuntu里安装Cloud Foundry时遇到错误信息&#xff0c;磁盘空间不够了&#xff1a; 使用这三个命令做了清理之后&#xff0c;结果依然不够理想&#xff1a; (1) sudo apt-get autoclean&#xff08;已卸载软件的安装包&#xff09; (2) sudo apt-get clean…

您好GroovyFX

GroovyFX汇集了我最喜欢的两件事&#xff1a; Groovy和JavaFX 。 GroovyFX项目主页面将GroovyFX描述为“ [为JavaFX 2.0提供Groovy绑定”。 该页面上进一步描述了GroovyFX&#xff1a; GroovyFX是一个API&#xff0c;它使在Groovy中使用JavaFX变得更加简单和自然。 GroovyFX专…

calc() ---一个会计算的css属性

最近这个月一直在赶项目开发&#xff0c;遇到的问题和学到的前端知识没有更新到博客园&#xff0c;现在闲了下来&#xff0c;就整理一下前端知识。  在项目开发中&#xff0c;在样式这方面花费的时间较多&#xff0c;因为针对于数字的变化特别多&#xff0c;本人不爱记数字&a…

HashMap实现原理及源码分析

HashMap实现原理及源码分析 哈希表&#xff08;hash table&#xff09;也叫散列表&#xff0c;是一种非常重要的数据结构&#xff0c;应用场景及其丰富&#xff0c;许多缓存技术&#xff08;比如memcached&#xff09;的核心其实就是在内存中维护一张大的哈希表&#xff0c;而H…

使用NetBeans Lambda支持在Java 8中使用Lambda表达式对列表进行排序

作为JSR 335的一部分&#xff0c; Lambda表达式已从Java 8开始引入Java语言&#xff0c;这是Java语言的一个重大变化。 如果您想了解更多关于Lambda表达式以及JSR 335的信息&#xff0c;可以访问以下资源&#xff1a; 在OpenJDK上的Lambda项目 。 Lambda常见问题解答 。 另一…

qq物联网 android sdk,物联网在腾讯:QQ物联

原标题&#xff1a;物联网在腾讯&#xff1a;QQ物联在物联网方面的一些产品&#xff0c;作为BAT三巨头之一&#xff0c;腾讯自然不能落后。本文就介绍一下腾讯的物联网平台&#xff0c;QQ物联。QQ物联&#xff1a;让每个设备成为一个QQ好友QQ物联的最大特点&#xff0c;就是让每…