首页> 搜索结果页
"api苏州" 检索
共 113 条结果
阿里云API创新大赛百万大奖花落谁家?
历时120天、120个参赛战队、100+参赛作品、100+理工类高校、1000+开发者报名参与,共同完成了云计算领域在中国区域的首个API as a Service Solution大赛。 阿里云API创新大赛于2017.02月正式启动,以API 经济的商业模式为模型,邀请广大参赛者共同探索中国的 API 经济。  6月11日下午13:30分,API as a Service Solution在上海云栖大会现场开启了终极PK,10个参赛战队在不同纬度展示了API经济强势回归带来的新型商业模式。   现场各战队及评委都做了深入的沟通交流,多个战队间沟通交流了初期合作意向。 苏州云图的API解决方案“随行云”(用户行为追踪、分析、营销)以清晰的产品命脉、精准的用户定位、明确的应用场景,广阔的商业价值,夺得了大赛特等奖,获得了百万大奖。   阿里云API创新大赛顺利落幕!这只是API经济开展国内市场迈出的第一步。   回顾大赛现场! API创新大赛的创始人阿里云资深专家净业为大赛终极PK赛做了开场: 云计算时代,开放成为了发展的趋势,越来越多的产品走向开放化。而API作为能力开放的核心载体,就此成为服务交付、能力复制、数据输出的最佳实践,已成为云计算市场增长最快的领域。 众多企业通过API的形式开始将跨平台、无状态的API用来作为系统间创建联系的通道,以消除前期系统建设的信息孤岛,从而降低协同成本,提升利润空间。          他还特别说到: 1、API经济就像早期的App Store一样能让小白们赚钱的,庸俗但有效。 2、API经济的发展让专业的人做专业的事,专注核心业务,打破专业壁垒。 3、API经济的发展打破了软硬件的天然结合。 API经济中企业不仅仅是API提供者,还可以是API的消费者。企业可以选用成熟、稳定的第三方API,来完善系统功能。这样不仅可以减少自身系统的代码、加快开发进度,且让开发人员有更多的时间处理自身领域的问题,这比重复构建别人已成熟的服务更有价值。如:支付、天气、图像识别等等,其他公司已经提供了标准的服务,并不需要我们再耗费人力物力重新开发一遍,并且我们耗时、耗力研发出来的也不一定有他人开发的稳定。 例如美国的一家做草地喷水头的公司,只会做硬件,但是发现太浪费水,于是他们简单调用了查询天气的API,下雨天它的喷淋头就不浇水了,给用户节省很多水费,那么用户自然而然就都选择了他们家的喷淋头,这家公司瞬间赚得盆钵满体。 那家硬件公司,没人会写天气API,也没什么好的软件人员,不用花几十万去雇佣一支团队专门开发天气API,而是花了“小钱”去查询天气公司的API,在成百上千万的喷淋头调用之下,显然API也赚到钱,他们也赚到市场。 像基于定位的API,其他人提供定位相关的各种场景,无须自己设置特定场景,把你们的API注册在API市场中,(可收费可免费)开放给大家,坐等用户和进帐。 API有较强的灵活性、拓展性和跨平台性,大环境在驱动着以API的形式输出服务、能力、数据,一组API就可支撑一个完整的服务或功能。 现场还迎来了9位专业级评委团,他们都是API领域的开拓者,是API经济的投资者,对API经济的发展前景持有敏锐的洞查力,他们为每个参赛作品都做了深入的点评,引导各战队在API领域有新的突破与发展,带动中国API市场的繁荣。 数空董事长王献旗先生说到:通过API,让开发者享受增值服务,来强化产品快速发展。但API化的模式是具有挑战性的,并非所有企业或者开发者在API化的路上都能够顺风顺水。这就需在企业的不同发展阶段,更懂得结合实际情况开放不同类型的API,采用不同的动作模式,再适情况择取合适的商业模式。 API创新大赛的落幕只是API经济在国内发展迈出的第一步,更是API经济的一个起点,国内API化程度还不成熟,如何将API经济成为撬动这个经济体中的一个“又粗又壮”的杠杆任重道远,但也更是先行者崛起的机会。     阿里云API创新大赛获奖名单: https://promotion.aliyun.com/ntms/act/apifame2017.html
文章
物联网  ·  大数据  ·  API  ·  云计算  ·  开发者
2017-06-15
API经济打通企业内外互通的任督二脉?
有人说 API 带动了 IOT 的增长,也有人说一个发展的 IOT 生态系统推动了强大的 API 及 API 经济的需求。实质上两者有着循环和共生的关系。 API帮助企业领导人实现公司转型、围绕现有产品打造新的生态系统,也变现核心资本、服务和产品。 进入互联网云计算时代,如何打破封闭的系统,让企业内外充分连接和互动?成了各企业管理者们面临的紧要问题。 让我们回到刚刚过去的6月11号阿里云上海云栖大会现场,让开发者回味无穷的莫过于阿里云API as a Service的现场PK赛。这场别开生面的API应用之争,使国内各中小企业开始认识到API经济对未来市场的商业地位,他们欲先进入API领域,抢占这第一杯羹。 聚合数据创始人张弥说到:阿里云本次举办API创新及应用大赛,让中国的企业和开发者们看到API在数据交换、服务交付方向的巨大潜力,通过API能更好地挖掘出数据的真正价值,带动市场新型商业模式。聚合数据也将主动与参赛者们进行沟通合作,共同开放服务,造福开发者,同时保持长期的与阿里云在API经济方向的协作。 现场VC及评委团老师,用他们敏捷的市场洞查力,以及对API经济即将带来的商业价值,为各参赛者做了中肯的点评及认可。 VC投资代表庚子基金投资总监韩佳雷说到:投资人关注的并不是你的技术有多牛逼,也不是您的系统构建的多漂亮。做为一款应用、产品,首先要考虑的是商业价值及投入产出比。你的产品解决了用户怎样的痛点?你的产品与用户能产生共鸣了吗?你的产品在用户需求上具有普遍性吗?你的产品竞争对手在哪里?只有弄清楚这些问题,才能在市这个市场上立足,有机会抢占市场。  “随行云”、“花伴侣”两个作品正好诠释了这四点。实至名归的获奖者,相信他们的产品会走的更快更远。 苏州图云创始人王之邑说到:目前API市场尚未完全打开,可以说是一片蓝海。我们认为不论是API或是关联的IoT都只是一个入口,真正有价值的都是藏在背后的数据。 通过本次API创新大赛带给我们很好的机会,可以向公众展示API产品的强大之处。过去由于API本身并不带有UI,也不直接面向用户,并不容易向客户说明这样的产品能带来怎样的体验。而本次大赛让公众更能了解API是完全能成为一个独立且强大的产品。 短期内我们会逐步丰富“随行云”背后数据,将国内主要4000家商场无线定位数据上线,满足移动广告平台客户需求。接下来将随行云上线到阿里云市场,以求与国内的广大开发者社群对接,激发更多使用创意。未来再加入机器学习的算法,让每一个用户手机都能随时随地为随行云更新自身数据!  “花伴侣”战队负责人赵继强先生说到:我们非常看好API市场的未来发展,这也是我们积极参与到本次大赛的一个原因。现阶段API市场还处在一个培育期,我们相信随着越来越多的API提供者的加入,开放API能够完成的工作会越来越多,也会越来越好,人们也会更加倾向于使用开放的API来构建自己的应用。 阿里云API创新大赛提供了一个非常好的平台,能够让更多的人了解我们的技术和产品,一些潜在用户通过大赛已经跟我们取得了联系,另外,在大赛过程中也结识一些行业专家以及其他团队优秀的队员,通过跟他们的交流我们也收获颇多。 接下来我们会持续完善“人工智能植物识别”这个基本能力,同时进一步扩展植物信息库,提供识别之外的附加价值,另一方面,通过花伴侣APP的运营,建立植物图片资源生产消费全流程的闭环,成为一个权威的植物信息平台。 我们还希望能更多的举办一些类似的活动,能够有更多的参赛队伍参与进来,后续能不能再举办一些基于现有API的产品开发大赛,不限于使用自己的API,通过大赛在实践中挖掘API的价值。 阿里云API创新大赛虽已落幕,但它留给我们的印象不单单是一个大赛。它切实有力地影响了API市场在国内的地位,打开了国内API认知的大门,给各中小企业者们带来了互通互惠的机会。它更像是个平台,而真正获益的是哪些API的提供商与使用者们。  API以简单便捷的调用方式,提高了开发效率,加强了系统的灵活性和可扩展性,提高了快速交付的能力。API成为驱动各行业数字化革命的催化剂将日渐明显,帮助企业创新打造新的数字生态系统,变现数字资产,挖掘数字化生产力。  
文章
应用服务中间件  ·  Linux  ·  API  ·  PHP  ·  Python
2017-06-22
中国移动苏州研发中心消息队列高可用设计之谈 | SOFAStack 用户说
前言: 高可用的定义,指的是“一个系统经过特有的设计与改造,减少因不确定故障停服的时间,从而对业务使用方来说可以保证其服务的高度可用性”。在生产环境中,往往会存在很多不可预知的故障因素,比如虚拟机宕机、磁盘损坏和网络故障等,因此系统自身的高可用是任何工业级产品所需重点考虑的因素。对于消息队列服务来说,考虑到故障切换和业务感知等问题,传统的高可用方式(冷备或者热备)一般都不太适用。在经过多种技术方案对比后,我们发现采用基于 Raft 共识算法的多副本设计方案可以满足我们产品的要求,因此在鉴权认证组件和API计量服务组件中,我们集成了蚂蚁金服开源的 SOFAJRaft 库,实现这两个组件应对单点故障的高可用。 GitHub 地址: https://github.com/alipay/sofa-jraft 一、背景知识:Raft 共识性算法是什么? Raft 是一种分布式系统中易于理解的共识算法,该协议本质上是 Paxos 算法的精简版,而不同的是依靠 Raft 模块化的拆分以及更加简化的设计,其实现起来更加容易和方便。[1]模块化的拆分主要体现在 Raft 把一致性协议划分为如下几部分: Leader 选举; Membership 变更; 日志复制; Snapshot。 而更加简化的设计则体现在:Raft 不允许类似 Paxos 中的乱序提交、简化系统中的角色状态(算法定义 Leader、Follower 和 Candidate 三种角色)、限制仅 Leader 可写入、采用随机超时触发 Leader Election 机制来避免“瓜分选票”等等。[2] 1.1 Raft 算法的整体结构概览 从上面的 Raft 算法整体结构图中可以看出,整个分布式系统中同一时刻有且仅有一个 Leader 角色的节点(如图最右边的服务器),只有 Leader 节点可以接受 Client 发送过来的请求。Leader 节点负责主动与所有 Follower 节点进行网络通信(如图左边两个服务器),负责将本地的日志发送给所有 Follower 节点,并收集分布式系统中多数派的 Follower 节点的响应。此外,Leader 节点,还需向所有 Follower 节点主动发送心跳维持领导地位(即:保持存在感)。所以,只要各个节点上的日志保持内容和顺序是一致的,那么节点上的状态机就能以相同的顺序执行相同的命令,这样它们执行的结果也都是一样的。 1.2 Raft算法的三个角色及转换 (1) Follower:完全被动,不能发送任何请求,只接受并响应来自 Leader 和 Candidate 的 Message,每个节点启动后的初始状态一般都是 Follower;(2)Leader:处理所有来自客户端的请求、复制 Log 到所有 Follower,并且与 Follower 保持心跳请求;(3)Candidate:节点竞选 Leader 时的状态。Follower 节点在参与选举之前,会将自己的状态转换为 Candidate。 1.3 任期与逻辑时钟概念 **(1)时间被划分为多个任期 term(如同总统选举一样),term id 按时间轴单调递增;(2)每一个任期开始后要做的第一件事都是选举 Leader 节点,选举成功之后,Leader 负责在该任期内管理整个分布式集群,“日志复制”、“通过心跳维护自己的角色”;(3)每个任期至多只有一个 Leader 节点,也可能没有 Leader (由于“分票”导致)。 1.4 Raft 算法的实际应用实现 目前,Raft 算法已经成熟地应用于诸多知名的开源项目中。业界非常著名的 Etcd(Kubernetes 高可用强一致性的服务发现组件)和 TiKV (高性能开源 KV 存储)均是 Raft 算法的实现。 二、BC-MQ 基于 Raft 的高可用设计 为满足企业上云和构建万物相连的物联网业务需求,中国移动苏州研发中心结合自身在云计算产品和技术的较多积累,研发了大云消息队列中间件产品 BC-MQ。该产品基于 Apache 开源社区的 RocketMQ 内核,同时结合云端 PAAS 产品架构和消息中间件的应用业务需求进行深度优化和定制化的研发,提供了一款可以满足于云端场景的高性能、高可靠、低延迟和高可用的工业级产品。本节从解决原有高可用技术方案的问题视角出发,同时结合选型 SOFAJRaft 库的缘由,将详细阐述 BC-MQ 产品中的安全认证和 API 计量采集服务的高可用设计方案(注:这里不会涉及到安全认证和 API 计量采集组件本身的技术方案细节)。 2.1 GlusterFS+Keepalived 高可用方案与问题 1. GlusterFS+Keepalived 高可用设计方案 在BC-MQ原有的方案中,多组安全认证服务各自独立部署组建集群,各个安全认证服务相互独立,没有主从关联,服务本身无状态,可水平任意扩展。安全认证服务的高可用依赖于RPC通信的客户端保证,其主要通过负载均衡算法从安全认证服务集群选择一个节点发送RPC请求来实现租户级鉴权认证元数据的获取。在生产环境中,如果出现其中一个安全认证节点宕机不可用时,客户端的RPC通信层能够及时感知并从本地的Node列表中剔除不可用节点。集群中有状态的租户级安全认证元数据的强一致性由GlusterFS分布式文件存储的同步机制来保证。安全认证服务组建高可用集群的具体设计方案图如下所示:体设计方案图如下所示:而BC-MQ中API计量采集服务组件的高可用性则是依靠Keepalived组件的冷备模式结合GlusterFS分布式文件存储的同步机制共同保证,从而在一定程度上解决了API计量采集服务的单点不可用问题。API计量采集服务的具体高可用设计方案图如下所示: 2. GlusterFS+Keepalived 高可用方案遇到的问题 初步看上面的这种高可用技术方案挺完美的。但是经过验证和仔细推敲后就发现在生产环境中可能会存在如下几个问题: 上面所述的高可用设计方案中引入了GlusterFS分布式文件存储系统和Keepalived组件,这增加了系统整体的运维复杂度,给运维人员带来很多人工介入排查和部署的工作负担;另一方面,GlusterFS和Keepalived本身的可靠性、稳定性和性能指标等问题也需要软件研发人员重点关注,这增加了系统整体设计的复杂度; 在实际的生产环境中,Keepalived组件采用冷备方式作为高可用方案需要考虑主机故障宕机后切换到备机的时间成本消耗。在这段时间内,API计量服务是短暂不可用的。因此,Keepalived组件的主备切换会造成业务感知影响,导致一些业务的风险发生。 2.2 基于 SOFAJRaft 库的高可用设计方案 由于“GlusterFS+Keepalived”的高可用方案存在上一节阐述的两个问题,所以我们考虑是否可以采用其他的高可用方案来解决这两个问题?目标:即使生产环境出现部分节点故障后,安全认证和API计量组件依旧能够正常提供服务,做到业务无感知。 为了实现当分布式集群中的部分节点出现故障停服后,集群仍然能够自动选主继续正常对外提供服务,使得故障对外部业务不会产生任何影响,同时高可用方案又不能依赖外部系统,那我们也就想到了Raft算法。Raft算法设计,简洁易懂,没有任何外部依赖,可以完成一个高可靠、高可用、强一致的数据复制系统,解决我们前面遇到的问题。 业界有一些Raft算法的实现,目前比较流行的主要有百度开源的Braft和蚂蚁金服开源的SOFAJRaft。从官方github上对两款开源Raft实现框架支持的功能和特性来看,基本相近,但Braft是C/C++语言实现的,而SOFAJRaft是JAVA语言实现的,因此我们从技术栈、集成难易和运维成本等角度综合考虑,最终选择了SOFAJRaft。 1. 为何技术选型 SOFAJRaft 库? SOFAJRaft 是一个基于 Raft 一致性算法的生产级高性能 JAVA 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。使用 SOFAJRaft,使用者可以更加专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 Raft 算法相关的技术难题,并且 SOFAJRaft 比较易于使用,用户可以通过 Github 上的几个示例在很短的时间内掌握并使用它。下面先简单介绍下 SOFAJRaft 的特性和增强功能点:其中: Membership change 成员管理:集群内成员的加入和退出不会影响集群对外提供服务。 Transfer leader:除了集群根据算法自动选出 Leader 之外,还支持通过指令强制指定一个节点成为 Leader。 Fault tolerance 容错性:当集群内有节点因为各种原因不能正常运行时,不会影响整个集群的正常工作。 多数派故障恢复:当集群内半数以上的节点都不能正常服务的时候,正常的做法是等待集群自动恢复,不过 SOFAJRaft 也提供了 Reset 的指令,可以让整个集群立即重建。 Metrics:SOFAJRaft 内置了基于 Metrics 类库的性能指标统计,具有丰富的性能统计指标,利用这些指标数据可以帮助用户更容易找出系统性能瓶颈。 为了提供支持生产环境运行的高性能,SOFAJRaft主要做了如下几部分的性能优化,其中: 并行append log:在 SOFAJRaft中 Leader本地持久化 Log 和向 Follower发送 Log 是并行的。 并发复制 Leader 向所有 Follwers 发送 Log 也是完全相互独立和并发的。 异步化:SOFAJRaft中整个链路几乎没有任何阻塞,完全异步的,是一个完全的 Callback 编程模型。 因此,综上所述我们最终选用SOFAJRaft的理由如下: SOFAJRaft基于JAVA实现,能够很方便的与BC-MQ中安全认证服务和API计量服务组件进行集成。 SOFAJRaft作为一个实现Raft协议的框架,提供了易于实现的状态机接口,只需要实现它提供的接口即可完成高可用的改造。 从实际的验证结果来说,SOFAJRaft的性能和稳定性能够完全满足甚至超过我们的预期。 SOFAJRaft的功能性能够解决上面篇幅中BC-MQ原有“GlusterFS+Keepalived”高可用方案中所遇到的问题。 2. BC-MQ 组件集成 SOFAJRaft 的优化设计 BC-MQ在集成SOFAJRaft库后在部署架构、数据持久化和高可用模式上都进行了能力升级,较好地解决了“GlusterFS+Keepalived”中的问题。 1.部署架构:集成SOFAJRaft库后,BC-MQ的安全认证和API计量服务的高可用部署不再依赖“GlusterFS+Keepalived”这两个外部组件;安全认证和API计量服务组件按照配置文件独立部署组成相应的RaftGroup即可对外提供服务;2.数据持久化:数据的强一致性不再依赖“GlusterFS分布式文件存储”。通过SOFAJRaft的日志复制和状态机,实现集群中Leader节点和Follower节点的数据同步保证主备节点的数据一致性;3.高可用模式:从原有的“KeepaLived冷备切换”转变为“Raft自动Leader选举”,发生故障后,API计量服务仍然能够对外正常提供服务,故障转移的过程无需运维人员介入。 组件服务端的状态机接口实现针对具体的业务应用而言(对 BC-MQ 来说,就是 API 计量统计和安全认证鉴权),状态机(StateMachine)是业务逻辑实现的主要接口,状态机运行在每个Raft节点上,提交的 任务 Task 如果成功,最终都会复制应用到分布式集群中的每个节点的状态机上。在 SOFAJRaft 中提供了一个已经具备绝大部分默认实现的抽象适配类— StateMachineAdapter,直接继承它可以使得业务应用避免实现所有的接口。我们根据 BC-MQ 组件改造的需求,对部分接口做了如下的实现:1. void onApply(Iterator iter):该方法是 SOFAJRaft 中最为核心的接口。在整个分布式集群环境中,待同步的数据会封装成 LogEntry 复制到其他节点。在数据同步完成之后,进程会提交到自身状态机的这个方法中执行。在 BC-MQ 中,API 计量采集服务在计量统计数据日志同步至 Follower 节点后,SOFAJRaft 在业务状态机的 onApply 方法中调用 API 计量采集服务组件的存储接口进行持久化。2. void onLeaderStart(long term)/void onLeaderStop(Status status):这个两个方法是在节点通过选举成为 Leader 和失去 Leader 资格时调用,BC-MQ 的安全认证和 API 计量服务组件本身也维护了 Raft 的角色状态(这里的角色状态与 SOFAJRaft 本身的是保持一致的)。在节点的角色发生转变的时候,需要调用这个方法,将组件的角色和状态转变一致。这样实现主要是与 BC-MQ 的业务场景相关,在集群中经过重新选举后节点角色转变时,只有API 计量组件服务的 Leader 节点才能够执行消息队列的 API 计量采集相关的定时任务。3. void onSnapshotSave(SnapshotWriter writer, Closure done)/boolean onSnapshotLoad(SnapshotReader reader):这两个方法是 SOFAJRaft 快照相关的接口调用,快照本身的作用就是在有新的节点加入到 SOFAJRaft Group 时,不需要加载全部的 Log 日志数据,而只需要从最近的 index 开始加载,这可以节省从 Leader 节点同步大量日志信息所造成的网络通信开销。BC-MQ 的安全认证和 API 计量采集服务组件实现了这两个方法,用于实现快照的特性。客户端请求重定向机制优化 SOFAJRaft 中默认只有 Leader 节点能够被客户端访问到,所有的日志提交都需要先提交到集群的 Leader 节点,然后由Leader节点同步到其他的 Follower 节点。BC-MQ 的安全认证服务和 API 计量服务组件通过 SOFAJRaft 改造后,在 BC-MQ 中原有的客户端 RPC 请求访问方式也需要经过一些优化设计,为了让客户端能够实时感知到分布式集群环境中当前的 Leader 节点,因此需要在客户端缓存一个集群的节点列表 NodeList 和 LeaderId。仅仅在客户端维护一个本地缓存还不够,因为如果集群中的 Leader 节点出现了宕机的故障时,集群会发生重新选举,那么客户端缓存的 Leader 节点信息就会过期,这就需要客户端就能够感知到 Leader 节点的变化。为解决这个问题,我们采用了 RPC 请求重定向机制来保证,一旦RPC请求发送到了集群中的 Follower 节点,那么 Follower 会将该请求重定向到 Leader。以下为 BC-MQ 客户端通信重定向机制优化设计图: 三、BC-MQ 的高可用与节点管理性验证 下面展示的是 BC-MQ 的安全认证服务和 API 计量服务组件的部分测试用例,从用例的实际执行情况来看,与我们的预期结果完全一致可以满足生产环境高可用的业务场景。  序号 具体业务场景 预期结果 实际结果 1 安全认证组件3节点部署,Kill掉其中1个节点,客户端持续发布/订阅带鉴权的消息 安全认证组件Leader角色转换,客户端发布/订阅带鉴权消息无任何影响 与预期一致 2 安全认证的5节点部署,Kill掉其中2个节点,客户端持续发布/订阅带鉴权的消息 安全认证组件Leader角色转换,客户端发布/订阅带鉴权消息无任何影响 与预期一致 3 API计量组件3节点部署,Kill掉其1个节点,客户端持续;发布/订阅带鉴权的消息 API计量组件Leader角色转换,输出的API计量文件正确 与预期一致 4 API计量组件5节点部署,Kill掉其2个节点,客户端持续发布/订阅带鉴权的消息 API计量组件Leader角色转换,输出的API计量文件正确 与预期一致 5 在集群中模拟出现网络分区(对称/非对称)的场景,安全认证服务集群是否会出现脑裂现象,鉴权认证数据是否正确 网络分区(对称/非对称)场景下,集群不会出现脑裂,并且鉴权数据是正确的 与预期一致 6 在集群中模拟出现网络分区(对称/非对称)的场景,API计量服务集群是否会出现脑裂现象,API计量数据是否正确 网络分区(对称/非对称)场景下,集群不会出现脑裂,并且API计量数据是正确的 与预期一致 7 在3节点组成的安全认证服务集群的负载工作的场景下,向该RaftGroup添加1/2节点,客户端持续发布/订阅带鉴权的消息 客户端发布/订阅带鉴权消息无任何影响 与预期一致 8 在5节点组成的安全认证服务集群的负载工作的场景下,移除该RaftGroup中的1/2节点,客户端持续发布/订阅带鉴权的消息 客户端发布/订阅带鉴权消息无任何影响 与预期一致 四、总结 本文主要介绍了中国移动苏州研发中心自主研发的 BC-MQ 产品中两个重要组件—安全认证和 API 计量服务是如何通过集成开源的 SOFAJRaft 库解决原来“GlusterFS+Keepalived”高可用方案中遇到的问题,以实现大规模消息队列云服务集群的高可用部署优化方案。由于文章篇幅的原因,本文没有对 BC-MQ 本身多项重要的特性进行详细介绍,作者将在后续的文章中继续进行阐述。同时,限于笔者的才疏学浅,对本文内容可能还有理解不到位的地方,如有阐述不合理之处还望留言一起探讨。 五、参考文献 []()[1] Diego Ongaro and John Ousterhout. Raft Paper. 2013[]()[2] https://mp.weixin.qq.com/s/zDusnG6WJGP0EX8UmbqtxQ 作者介绍:胡宗棠,中国移动苏州研发中心云计算中间件团队负责人,Apache RocketMQ Committer,Linux OpenMessaging Advisory Borad Member,SOFAJRaft Contributor,熟悉分布式消息中间件的设计原理、架构以及各种应用场景。
文章
API  ·  安全  ·  算法  ·  消息中间件  ·  运维
2019-08-14
怎样用Python实现地理编码
引言 今天看到一篇阿里云的文章"天下武功,唯快不破",以物流行业为例,分析了PostgreSQL 与 Greenplum 在地理位置信息处理,最佳路径算法,机器学习等方面的物流行业应用方法。其中提到了地址转换成坐标的问题,更专业些的名词应该是“地理编码”,即知道一个地址,如北京市海淀区上地十街10号,怎么样可以获取到对应的经纬度位置信息(40,116),或者反过来。 地理编码概念 很多地图相关的厂商都提供了相关的API,我们可以直接利用这些API得到这些信息。比如百度的Geocoding API。 Geocoding API是一类接口,用于提供从地址到经纬度坐标或者从经纬度坐标到地址的转换服务,用户可以使用C# 、C++、Java等开发语言发送请求且接收JSON、XML的返回数据。Geocoding API包括地址解析和逆地址解析功能: 借用ESRI文档中更直观的一张图 地理编码: 即地址解析,由详细到街道的结构化地址得到百度经纬度信息,例如:“北京市海淀区中关村南大街27号”地址解析的结果是lng:116.31985,lat:39.959836 同时,地理编码也支持名胜古迹、标志性建筑名称直接解析返回百度经纬度,例如:“百度大厦”地址解析的结果是lng:116.30815,lat:40.056885 逆地理编码: 即逆地址解析,由百度经纬度信息得到结构化地址信息,例如:“lat:31.325152,lng:120.558957”逆地址解析的结果是“江苏省苏州市虎丘区塔园路318号”。 不过,需要说明的一点是,若想使用百度的这套API的前提是,有百度账号并申请相应的Key。其实,除了百度之外,谷歌、ESRI、微软的Bing等都有类似的地理编码服务。不过这些服务大多没有专门针对Python的库并且彼此之间的Json结构也不一致。于是乎专治不服的Python大神做了一个专门的地理编码工具geocoder,将这些不同厂商的服务整合统一起来。 地理编码工具geocoder 首先看一下它都支持哪些公司的地理编码服务: Provider Optimal Usage Policy ArcGIS World Baidu China API key Bing World API key CanadaPost Canada API key FreeGeoIP World Geocoder.ca CA & US Rate Limit GeocodeFarm World Policy GeoNames World Username GeoOttawa Ottawa Google World Rate Limit, Policy HERE World API key IPInfo World Mapbox World API key MapQuest World API key Mapzen World API key MaxMind World OpenCage World API key OpenStreetMap World Policy Tamu US API key TomTom World API key What3Words World API key Yahoo World Yandex Russia TGOS Taiwan 安装 pip install geocoder 地理编码 import geocoder g = geocoder.google("1403 Washington Ave, New Orleans, LA 70130") g = geocoder.arcgis(u"北京市海淀区上地十街10号") g.latlng 输出为 [29.9287839, -90.08421849999999] 也可以查看完整的geojson g.geojson 输出为 {'bbox': [-90.0855674802915, 29.9274349197085, -90.0828695197085, 29.9301328802915], 'geometry': {'coordinates': [-90.08421849999999, 29.9287839], 'type': 'Point'}, 'properties': {'accuracy': u'ROOFTOP', 'address': u'1403 Washington Ave, New Orleans, LA 70130, USA', 'bbox': [-90.0855674802915, 29.9274349197085, -90.0828695197085, 29.9301328802915], 'city': u'New Orleans', 'confidence': 9, 'country': u'US', 'county': u'Orleans Parish', 'encoding': 'utf-8', 'housenumber': u'1403', 'lat': 29.9287839, 'lng': -90.08421849999999, 'location': '1403 Washington Ave, New Orleans, LA 70130', 'neighborhood': u'Garden District', 'ok': True, 'place': u'ChIJGyFHWc2lIIYRYSoneaXAUiw', 'postal': u'70130', 'provider': 'google', 'quality': u'street_address', 'state': u'LA', 'status': 'OK', 'status_code': 200, 'street': u'Washington Ave'}, 'type': 'Feature'} 直接用Google尝试查询中文地址时失败 g = geocoder.google(u"北京市海淀区上地十街10号") g.ok 输出为 False 用百度应该没问题,不过我没有申请相应的key。切换到arcgis,能够成功编码 g = geocoder.arcgis(u"北京市海淀区上地十街10号") g.latlng 输出为 [40.050934, 116.30079] 逆地理编码 g = geocoder.google([29.9287839, -90.08421849999999], method='reverse') print g.address print g.city print g.state print g.country 输出为 1403 Washington Ave, New Orleans, LA 70115, USA New Orleans LA US 换成中国的地址 g = geocoder.google([40.050934, 116.30079], method='reverse') print g.address print g.city print g.state print g.country 输出为 Bai Du Da Sha, Haidian Qu, Beijing Shi, China, 100193 Beijing Beijing Shi CN 用arcgis的服务试试 g = geocoder.arcgis([40.050934, 116.30079], method='reverse') print g.address print g.city print g.state print g.country 输出为 None 北京市 北京市 CHN Google转换成的是英文,但地址比较全。arcgis虽然是中文,但是详细的地址居然输出为了None,这有个X用。 其他 geocoder 的功能不止于此,它还可以查IP(包括自己的)。 g = geocoder.ip('199.7.157.0') print g.latlng print g.city g = geocoder.ip('me') print g.latlng print g.city 输出为 [43.6934, -79.4857] Toronto [51.05, 13.75] Dresden 查询一个城市的空间包围盒 g = geocoder.arcgis(u"山东") g.bbox 输出为 {'northeast': [38.976997, 121.976998], 'southwest': [33.022997, 116.022998]} 小结 空间信息既可以利用行政区划、自然地理区域等文本信息描述,也可以用坐标系统、数字(邮编等)来标识。利用地理编码技术,可以将空间信息的地理定位要素与相应的文本信息关联起来。本文主要介绍了geocoder地理编码这一小工具,可以方便快捷地利用地图等相关厂商提供的地理编码服务,将文字描述的位置转换成地图上的经纬度,或者通过地图上的某个位置坐标获得相应的位置信息文字描述。 原文发布时间为:2016-11-18  本文作者:时空Drei 本文来自云栖社区合作伙伴“Python中文社区”,了解相关信息可以关注“Python中文社区”微信公众号
文章
API  ·  定位技术  ·  数据格式  ·  Python
2018-03-14
【百度地图API】JS版本的常见问题
原文:【百度地图API】JS版本的常见问题 【新手必读】API常见问题   2011-12-12   1、请问如何将我的店铺标注在百度地图上?我是否可以做区域代理?在百度地图上标注是否免费?   答复: 这里只负责API的技术咨询,不解决任何地图标注问题。在百度地图上标注自己公司,即气泡标注业务。该业务已外包给青岛亿搜,不属于API技术范畴。全国商户的标注需求,请致电:合作伙伴“青岛亿搜网络科技有限公司”咨询。联系电话:0532-66066999 另外,百度地图目前不再对外发展区域代理。因为我们地图整体还是朝免费标注的方向发展,因此不再对外拓展标注代理业务。 个人标注业务已外包给青岛亿搜,由他们进行标注和收费事宜。如果您是上市公司,或者像7天、如家、肯德基一类的连锁行业,百度地图运营团队会主动联系您进行合作标注。如果您是个人标注,请致电青岛亿搜0532-66066999。     2、是否可以去掉百度右下角的版权?百度地图API是否免费?   回复: 首先,版权是必须要保留的哦~这是免费使用的条件之一。其次,只要是非商业的地图应用,都是可以免费使用的。详见百度API使用条款: http://dev.baidu.com/wiki/map/index.php?title=%E4%BD%BF%E7%94%A8%E6%9D%A1%E6%AC%BE  百度地图API对于用户的非商业应用是不收取任何费用的。如果您不了解非商业应用的含义,请发邮件致mapapi@baidu.com,有专人为您解答。     3、如何反馈地图数据错误?我有技术问题,应该去哪儿咨询?   回复: 地图数据错误或更新请反馈至:http://tousu.baidu.com/map/add  API合作联系电话:010-59921813 或mapapi#baidu.com(请把#换成@)   API技术咨询(百度Hi群)请先下载百度HI聊天工具 JS版:1357363 移动版:1363111 移动定位API:1374928   在百度地图标注,致电青岛亿搜:0532-66066999     4、我是从GPS/谷歌转到百度来的,为什么有坐标偏移?请问如何进行坐标转换?如何批量转换坐标?   回复: 针对IOS、android和javascript,有三个版本的坐标转换文档。请将您的公司名称、项目名称、项目简介、联系人和联系方式,发邮件至mapapi@baidu.com咨询。   坐标转换工具: http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&0_5#0&5    公司名称 项目名称:(web  or web  or  客户端(IOS  or  android  or  others)应用请写清楚+哪两种坐标系的转换请说明)  项目情况:  请描述清楚项目具体的应用场景 联系人 联系电话     烦请您说明以上情况,谢谢     5、利用百度地图API如何进行定位?用手机怎样定位?如何在地图上定位?   回复: javascript版本定位代码: http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&7_31#7&31  移动平台(S60、android、IOS)的定位API: http://dev.baidu.com/wiki/geolocation      6、百度地图API在访问频率上,是否有限制?   回复: 用户同时在线数:按每秒初始JS获取来计算,支持每秒1000~1500次。 查询性能,按不同查询服务分: 检索服务支持800次/秒; 公交、驾车服务400~600次/秒; 地理编码性能支持150次/秒。 带宽限度:由于API数据量较小,完全支持服务。 坐标转换接口:单ip 50次/秒  (超过100次返回403错误) 静态图:独立IP 10个/秒     7、标注过多时,地图在IE中速度会下降,尤其是IE6。如何解决?   回复: 标注数据量请控制在150个以内,以保持高性能。 标注数量在260以内,可以使用自定义覆盖物实现。 标注数量大于300个,建议尝试marker聚合,或者数据抽希。   Marker聚合:http://tieba.baidu.com/f?kz=1031097376  数据抽希:比如有10个marker,选择其中6个做为显示点。   我的建议是,不要一次在地图上添加过多的marker,而是先把point存储在数据库里,当需要显示某个marker是,再addOverlay。     8、关于“地址解析和模糊查询”的问题。 我用地址解析查询“北京市”为什么不返回城市?我用search(模糊查询或者智能查询)查询“重庆市江北区郭家沱红江村23-1”为什么出来很多个结果?   回复: 首先,上述问题的两种查询显然是不对了,用错了接口。使用地址解析,应该查询详细到门牌号的地址,这样会返回一个确定的地址。而使用search(模糊查询或者智能查询),可以搜索任意的关键词,注意,需要带上城市名称。   下面来看详解: 地址解析:只对详细到街道的地址进行解析。不要搜索不详细的地名,如“北京市”。 使用Geocoder进行地址解析,比如“北京市海淀区上地10街”,当系统匹配到这个地址时,getPoint就会返回一个坐标点。这里需要用到回调函数。当系统无法匹配“北京市海淀区上地10街”的时候,会返回“北京市海淀区”的几何中心点,如果还是无法匹配,会返回“北京市”的几何中心点。 地址解析示例:http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&7_29#7&29     search(模糊查询或者智能查询):如果你只是想返回“北京市”的坐标,或者说想要模糊查询/智能查询,比如“北京市肯德基”,建议不要使用地址解析,而是使用LocalSearch类的search方法。 智能搜索例子详见:http://www.cnblogs.com/milkmap/archive/2011/04/27/2030971.html      9、如何制作自定义图标?安居客、酷讯上的图标是如何制作的?房产标注,银行标注是如何制作的?   一种是只有图片,比如银行标注,《更换icon的marker》示例和教程如下: http://www.cnblogs.com/milkmap/archive/2011/03/01/1967885.html    第二种是,图片加上文字的图标,像房产标注,《自定义覆盖物》示例和教程如下: http://www.cnblogs.com/milkmap/archive/2011/04/18/2019906.html    第三种是,用label来模拟自定义覆盖物,《用label制作简易的房产标签》示例和教程如下: http://www.cnblogs.com/milkmap/archive/2011/08/24/2151073.html     10、如何利用百度地图API进行对自己数据的搜索?   回复: 一是前端搜索,示例和教程如下: http://www.cnblogs.com/milkmap/archive/2011/06/24/2089102.html    二是自己建立数据库。储存数据为:id、经纬度(Point)、名称(如百度大厦)、地址电话等信息。当用户输入关键字(如百度大厦)时,利用SQL在数据库中查找该条数据,并返回经纬度(Point)信息。然后用添加覆盖物的示例,把点打到地图上去,并且把地址电话信息等内容添加到信息窗口里。添加覆盖物示例和打开信息窗口示例如下: http://www.cnblogs.com/milkmap/archive/2011/08/16/2135323.html      11、如何清除地图上所有的标注?如何清除单个标注?自定义覆盖物如何清除?   回复: 清除地图上所有的标记,用map.clearOverlays(); 清除单个标注iMarker,用map.removeOverlay(iMarker); 显示和隐藏自定义覆盖物,可以继承overlay的hide();或者show()方法。    12、是否有离线地图?   回复: 目前只有手机离线地图,下载后可以在客户端导入。地址:http://shouji.baidu.com/map/map.html?from=1052     13、如何显示城市的轮廓?   回复:可以使用boundary接口。注意,该接口目前只使用于API1.1。 类参考: http://dev.baidu.com/wiki/map/index.php?title=Class:%E5%9F%BA%E7%A1%80%E7%B1%BB/Boundary  示例,请参照教程第三部分: http://www.cnblogs.com/milkmap/archive/2011/04/15/2017135.html      14、如何在C\PHP\JAVA\ASP中调用API?   回复: 百度地图API是由javascript开发的前端接口,任何后端语言都可调用,包括但不限于C、PHP、ASP、JAVA。 如果您需要更多形式的API,请查看API综合产品首页,8种形式的地图API欢迎您的使用。API综合产品首页: http://dev.baidu.com/wiki/static/index.htm      15、我采集的GPS数据转换成百度坐标系后,偏差非常大。请问怎么解决?   回复: 有以下四种可能: A、原始坐标可能不是GPS(即wgs84)的 解决方案:请确保采集到的数据时WGS84的标准。   B、原始坐标准确度不够 解决方案:请确保采集GPS数据时,搜到至少4颗以上的卫星。并且GPS数据准不准,还取决于周围建筑物的高度,越高越不准,因为有遮挡。   C、度分秒的概念混淆 比如,在google earth上采集到的是39°31'20.51'',那么应该这样换算,31分就是31/60度,20.51秒就是20.51/3600度,结果就是39 + 31/60 + 20.51/3600 度。   D、经纬度顺序写反了 百度坐标是先经度,再维度,即Point(lng, lat)。谷歌坐标的顺序恰好相反,是(lat, lng)。   附上,百度的坐标转换工具: http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&0_5#0&5      16、Place API有什么功能?   回复: Place API主要功能是做数据管理。用户自己不用存储数据,只需调用我们的各个分类数据ID,就可以了。还能做深度的检索定制;根据自己的需求出检索排序。   17、除了官网外,还有哪些地方可以学习百度地图API技术?   回复: 官网示例学习:http://dev.baidu.com/wiki/static/map/API/examples/  API实践教程:http://www.cnblogs.com/milkmap/  API开发机制:http://www.cnblogs.com/jz1108/      18、我有大批坐标需要转换,应该如何操作呢?   回复: 首先有两种坐标转换接口供使用,单次接口和批量接口。单次接口的限制是独立IP 50次/秒,可同时请求多次;批量接口是请求一次,最多返回20个坐标。 单次示例: http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&0_5#0&5  批量示例: http://dev.baidu.com/wiki/static/map/API/examples/?v=1.2&0_6#0&6    推荐一种方法: 每隔10秒取一次GPS坐标,存在队列中备用。 使用单次或者批量坐标转换接口,每秒执行一次,将队列中的坐标进行转换。   19、交通流量、三维地图、打车费、卫星图是什么情况?   回复: 三维地图: 北京 上海 广州 深圳   打车费用: 北京,上海,广州,深圳,成都、天津、杭州、武汉、苏州、南京、重庆、郑州、西安、济南、青岛、长沙。   交通流量: 北京,上海,广州,深圳,南京,南昌,成都,重庆,武汉,大连,常州   卫星图覆盖级别: 上线的卫星图包含高中低三个分辨率的卫星图,覆盖1-19级底图,其中1-7级为低分辨率全球覆盖,8-13级为中分辨率全中国覆盖,14-19级为高清分辨率部分城市重点区域覆盖。我们将陆续更新上线共339个城市。   20、如何通过经纬度查询到已加上的标注(Marker)?   回复:Marker 是通过map.AddOverlay()加上的,所以也用map可以得到加上的所有Marker: var mkrs = map.getOverlays(); for (var i=1; i<mkrs.length;i++) { ... } 要注意的是,假如用BMapLib.MarkerTool加上3个Marker后,map中含有的marker数量为4,第一个(mkrs[0])应该是MarkerTool。所以遍历Marker要从1到length   21、
文章
前端开发  ·  JavaScript  ·  API  ·  定位技术  ·  编解码  ·  Android开发  ·  iOS开发  ·  Java  ·  PHP  ·  存储
2015-01-07
【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍
原文:【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍 摘要: 在这篇文章中,你将学会,如何利用百度地图API进行标注。如何使用API新增的打车费用接口。 ------------------------------------------------------------------------------------------------------- 哇,好久没有上来了。主要是因为最近工作繁忙,加上休息时间被各种排练、社团活动占满,导致木有更新此博客。 突然发现,【百度地图API】原来有了新变化!新增了打车费用的接口! 同时,坐标拾取工具也有了很大的变化!必须介绍一下啦~~ 好!就让我们一起来看看吧~ ------------------------------------------------------------------------------------------------------- 一、如何进行标注 1、首先,我们需要找准标注的位置。比如,我想标注“中央民族大学”附近的网球场。那么,我转到坐标拾取工具页面,请点击http://openapi.baidu.com/map/pick/index.html 。输入“中央民族大学”,点击查找按钮。 2、现在,地图上出现了很多中央民族大学附近的红色标注点。我将鼠标移到网球场上,就能看到我想要获取的地点的坐标。我就记下这个坐标。比如“116.330599,39.95536”。 3、坐标反查 如果,我已经有了一个从别的地方得到的坐标点,比如说“116.414597,39.955441”,而我不知道这是哪个地方的坐标。我可以把这个输入到地址框中,勾选反向查询,然后点击百度一下的按钮。地图上就会出现这个地点是在安定门。 坐标反查比较适合在得知坐标,而不知具体位置的时候使用。比如,你用GPS定位了一个点,你得到了这个点的经纬度,就可以利用坐标反查来获得这个点的地址。 从GPS坐标转换到百度地图坐标,需要咨询下百度api的客服:mapapi@baidu.com 4、建立网页文件 打开记事本,粘贴以下代码至记事本中,保存文件为map.htm文件。(注意这里的格式是网页文件的格式哦) 大家动手来粘贴一下。 <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>酸奶小妹——百度地图API学习</title><style type="text/css"> html{height:100%} body{height:100%;margin:0px;padding:0px} #milkMap{height:400px;width:600px;border:1px solid blue;}</style><script type="text/javascript" src="http://api.map.baidu.com/api?v=1.1&services=false">  </script></head><body><div id="milkMap"></div><script type="text/javascript">var map = new BMap.Map("milkMap"); // 创建地图实例 var point = new BMap.Point(116.330599,39.95536); // 创建点坐标 map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别   var marker = new BMap.Marker(point); // 创建标注    map.addOverlay(marker); // 将标注添加到地图中 </script></body></html>  注意,在这句话里,写上你查询到的坐标。 var point = new BMap.Point(116.330599,39.95536); // 创建点坐标 5、给标注加上信息窗口 在刚才代码的基础上,加上以下内容,就可以实现点击标注,弹出信息窗口的事件了。比如以下这个例子: var infoWindow = new BMap.InfoWindow("<a target='_blank' href='http://www.ui-love.com/su/'><img title='粟摄影' alt='粟摄影' src='http://ui-love.com/static/img/subslogan.jpg' /></a>"); // 创建信息窗口对象marker.addEventListener("click", function(){ //给标注添加点击事件 this.openInfoWindow(infoWindow);}); 你可以在信息窗口里放图片,文字,链接等等。任何html的东西都在信息窗口里面,从而完成整个标注的过程。 全部代码在此: 标注代码 <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>酸奶小妹——百度地图API学习</title><style type="text/css"> html{height:100%} body{height:100%;margin:0px;padding:0px} #milkMap{height:400px;width:600px;border:1px solid blue;}</style><script type="text/javascript" src="http://api.map.baidu.com/api?v=1.1&services=false">  </script></head><body><div id="milkMap"></div><script type="text/javascript">var map = new BMap.Map("milkMap"); // 创建地图实例 var point = new BMap.Point(116.330599,39.95536); // 创建点坐标 map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别   var marker = new BMap.Marker(point); // 创建标注    map.addOverlay(marker); // 将标注添加到地图中 var infoWindow = new BMap.InfoWindow("<p><a target='_blank' title='粟摄影' alt='粟摄影' href='http://www.ui-love.com/su/'><img src='http://ui-love.com/static/img/subslogan.jpg' /></a></p><p style='font-size:12px;'>欢迎光临<b>粟摄影</b>的官方网站>></p><p style='font-size:12px;'>电话:010-8888 6666</p><p style='font-size:12px;'>地址:北京市海淀区XX门XX街道XXX村子</p>"); // 创建信息窗口对象 marker.addEventListener("click", function(){ //给标注添加点击事件 this.openInfoWindow(infoWindow); });</script></body></html> --------------------------------------------------------------------------------------------------- 二、打车费用接口 举个例子来说明这个接口是如何使用的吧。比如,我要从安定门到王府井。 1、找到打车费用的接口 我先到API的官方网站,找到类参考的入口 http://openapi.baidu.com/map/classReference.html  打车查询是属于“服务类”,我就点击一下“服务”。然后就看到这样的页面。 调用该接口就可以可以啦~ 说明:TaxiFare有白天和夜晚两种计算费用的方式。你还可以查询起步价、单价和总价。 taxiFare.day.totalFare //白天打车总价taxiFare.night.totalFare //夜间打车总价taxiFare.day.initialFare //白天的起步价taxiFare.day.unitFare //白天的单价taxiFare.night.initialFare //夜间的起步价taxiFare.night.unitFare //夜间的单价 完整的白天打车总价的例子如图,源代码在图的下方。 打车 <html><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><title>驾车导航</title><script type="text/javascript" src="http://api.map.baidu.com/api?key=46ce9d0614bf7aefe0ba562f8cf87194&v=1.1&services=true"></script></head><body><div style="width:520px;height:340px;border:1px solid gray" id="container"></div></body></html><script type="text/javascript">var map = new BMap.Map("container"); //定义地图容器map.centerAndZoom(new BMap.Point(116.404, 39.915), 14); //初始化地图var driving = new BMap.DrivingRoute(map, {onSearchComplete:yyy,renderOptions:{map: map, autoViewport: true}}); driving.search("安定门", "王府井"); //驾车查询function yyy(rs){ alert("从安定门到王府井打车总费用为:"+rs.taxiFare.day.totalFare+"元"); //计算出白天的打车费用的总价}</script> 目前,打车费用支持的城市为:北京,上海,广州,深圳,成都、天津、杭州、武汉、苏州、南京、重庆、郑州、西安、济南、青岛、长沙。 自定义驾车的例子,请参考:http://www.cnblogs.com/milkmap/archive/2010/12/21/1912978.html
文章
Web App开发  ·  JavaScript  ·  前端开发  ·  API  ·  定位技术
2015-01-07
常用的API接口,返回JSON格式的服务API接口
物流接口 快递接口: http://www.kuaidi100.com/query?type=快递公司代号&postid=快递单号 ps:快递公司编码:申通="shentong" EMS="ems" 顺丰="shunfeng" 圆通="yuantong" 中通="zhongtong" 韵达="yunda" 天天="tiantian" 汇通="huitongkuaidi" 全峰="quanfengkuaidi" 德邦="debangwuliu" 宅急送="zhaijisong" 谷歌接口 FeedXml转json接口: http://ajax.googleapis.com/ajax/services/feed/load?q=Feed地址&v=1.0 备选参数:callback:&callback=foo就会在json外面嵌套foo({})方便做jsonp使用。 备选参数:n:返回多少条记录。 百度接口 百度百科接口: http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=关键字&bk_length=600 查询出错示例如下:查看原始页面 {"error_code":"20000","error_msg":"search word not found"} 天气接口 百度接口: http://api.map.baidu.com/telematics/v3/weather?location=嘉兴&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ location:城市名或经纬度 ak:开发者密钥 output:默认xml 气象局接口: http://m.weather.com.cn/data/101010100.html 新浪接口: http://php.weather.sina.com.cn/iframe/index/w_cl.php?code=js&day=0&city=&dfc=1&charset=utf-8 参数中city如果给了参数就是相关的城市,否则会自动判断 day=0的话是今天 返回的参数 大家看着办吧,具体的我也不清楚,新浪没给API。 音乐接口 虾米接口 http://kuang.xiami.com/app/nineteen/search/key/歌曲名称/diandian/1/page/歌曲当前页?_=当前毫秒&callback=getXiamiData QQ空间音乐接口 http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?uin=QQ号码&json=1&g_tk=1916754934 QQ空间收藏音乐接口 http://qzone-music.qq.com/fcg-bin /fcg_music_fav_getinfo.fcg?dirinfo=0&dirid=1&uin=QQ号& p=0.519638272547262&g_tk=1284234856 多米音乐接口 http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=关键字&pi=页码&pz=每页音乐数 soso接口 http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=关键字&perpage=1&ie=utf-8 视频接口 土豆接口 http://api.tudou.com/v3/gw?method=album.item.get& appKey=Appkey&format=json&albumId=视频剧集ID&pageNo=当前页& pageSize=每页显示 地图接口 阿里云根据地区名获取经纬度接口 http://gc.ditu.aliyun.com/geocoding?a=苏州市 参数解释: 纬度,经度 type 001 (100代表道路,010代表POI,001代表门址,111可以同时显示前三项) 阿里云根据经纬度获取地区名接口 http://gc.ditu.aliyun.com/regeocoding?l=39.938133,116.395739&type=001 获取用户的IP,国家代码缩写,经纬度 http://www.telize.com/geoip?callback=a 参数解释: callback是回调函数 IP接口 新浪接口(ip值为空的时候 获取本地的) http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255 淘宝接口 http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42 手机信息查询接口 淘宝网接口 http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=手机号 拍拍接口 http://virtual.paipai.com/extinfo/GetMobileProductInfo?mobile=手机号&amount=10000&callname=getPhoneNumInfoExtCallback 百付宝接口 https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=手机号 115接口 http://cz.115.com/?ct=index&ac=get_mobile_local&callback=jsonp1333962541001&mobile=手机号 有道接口 http://www.youdao.com/smartresult-xml/search.s?jsFlag=true&type=mobile&q=手机号 手机在线接口 http://api.showji.com/Locating/www.showji.com.aspx?m=手机号&output=json&callback=querycallback 视频信息接口 优酷 http://v.youku.com/player/getPlayList/VideoIDS/视频ID (比如 http://v.youku.com/v_show/id_XNTQxNzc4ODg0.html的ID就是XNTQxNzc4ODg0) 翻译、词典接口 腾讯 http://dict.qq.com/dict?q=词语 腾讯的部分接口 获取QQ昵称和用户头像 http://r.qzone.qq.com/cgi-bin/user/cgi_personal_card?uin=QQ(不过是jsonp哦)   其他接口 360笑话接口 http://xiaohua.hao.360.cn/m/itxt?page=1&callback=jsonp7
文章
JSON  ·  API  ·  PHP  ·  数据格式
2015-10-26
云原生网关 APISIX 的核心流程以源码分析的方式剖析其工作原理
云原生网关 APISIX 的核心流程以源码分析的方式剖析其工作原理✨博主介绍APISIX介绍:特性项目概述生态概述基本流程目录结构启动流程基本类型操作字符串Table工具类JSON 操作LRU 缓存后台任务请求生命周期ctxheadersetcd初始化数据校验后台数据同步配置同步Router路由构建路由初始化路由匹配Balancer服务发现负载均衡Plugin插件加载插件匹配插件执行主流程init_by_luainit_worker_by_luaaccess_by_lua一些思考边缘计算ServerlessWebAssemblyService Mesh💫点击直接资料领取💫✨博主介绍🌊 作者主页:苏州程序大白🌊 作者简介:🏆CSDN人工智能域优质创作者🥇,苏州市凯捷智能科技有限公司创始之一,目前合作公司富士康、歌尔等几家新能源公司💬如果文章对你有帮助,欢迎关注、点赞、收藏💅 有任何问题欢迎私信,看到会及时回复💅关注苏州程序大白,分享粉丝福利APISIX介绍:Apache APISIX 是一个动态、实时、高性能的 API 网关, 提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。你可以使用 Apache APISIX 来处理传统的南北向流量,以及服务间的东西向流量, 也可以当做 k8s ingress controller 来使用。Apache APISIX 的技术架构如下图所示:特性你可以把 Apache APISIX 当做流量入口,来处理所有的业务数据,包括动态路由、动态上游、动态证书、 A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵御恶意攻击、监控报警、服务可观测性、服务治理等。全平台云原生: 平台无关,没有供应商锁定,无论裸机还是 Kubernetes,APISIX 都可以运行。运行环境: OpenResty 和 Tengine 都支持。支持 ARM64: 不用担心底层技术的锁定。多协议TCP/UDP 代理: 动态 TCP/UDP 代理。Dubbo 代理: 动态代理 HTTP 请求到 Dubbo 后端。动态 MQTT 代理: 支持用 client_id 对 MQTT 进行负载均衡,同时支持 MQTT 3.1.* 和 5.0 两个协议标准。gRPC 代理:通过 APISIX 代理 gRPC 连接,并使用 APISIX 的大部分特性管理你的 gRPC 服务。gRPC 协议转换:支持协议的转换,这样客户端可以通过 HTTP/JSON 来访问你的 gRPC API。Websocket 代理Proxy ProtocolDubbo 代理:基于 Tengine,可以实现 Dubbo 请求的代理。HTTP(S) 反向代理SSL:动态加载 SSL 证书。全动态能力热更新和热插件: 无需重启服务,就可以持续更新配置和插件。代理请求重写: 支持重写请求上游的host、uri、schema、enable_websocket、headers信息。输出内容重写: 支持自定义修改返回内容的 status code、body、headers。Serverless: 在 APISIX 的每一个阶段,你都可以添加并调用自己编写的函数。动态负载均衡:动态支持有权重的 round-robin 负载平衡。支持一致性 hash 的负载均衡:动态支持一致性 hash 的负载均衡。健康检查:启用上游节点的健康检查,将在负载均衡期间自动过滤不健康的节点,以确保系统稳定性。熔断器: 智能跟踪不健康上游服务。代理镜像: 提供镜像客户端请求的能力。流量拆分: 允许用户逐步控制各个上游之间的流量百分比。精细化路由支持全路径匹配和前缀匹配支持使用 Nginx 所有内置变量做为路由的条件,所以你可以使用 cookie, args 等做为路由的条件,来实现灰度发布、A/B 测试等功能支持各类操作符做为路由的判断条件,比如 {"arg_age", ">", 24}支持自定义路由匹配函数IPv6:支持使用 IPv6 格式匹配路由支持路由的自动过期(TTL)支持路由的优先级支持批量 Http 请求支持通过GraphQL属性过滤路由安全防护多种身份认证方式: key-auth, JWT, basic-auth, wolf-rbac。IP 黑白名单Referer 白名单IdP 支持: 支持外部的身份认证服务,比如 Auth0,Okta,Authing 等,用户可以借此来对接 Oauth2.0 等认证方式。限制速率限制请求数限制并发防御 ReDoS(正则表达式拒绝服务):内置策略,无需配置即可抵御 ReDoS。CORS:为你的 API 启用 CORS。URI 拦截器:根据 URI 拦截用户请求。请求验证器。运维友好OpenTracing 可观测性: 支持 Apache Skywalking 和 Zipkin。对接外部服务发现:除了内置的 etcd 外,还支持 Consul 和 Nacos,以及 Eureka。监控和指标: Prometheus集群:APISIX 节点是无状态的,创建配置中心集群请参考 etcd Clustering Guide。高可用:支持配置同一个集群内的多个 etcd 地址。控制台: 操作 APISIX 集群。版本控制:支持操作的多次回滚。CLI: 使用命令行来启动、关闭和重启 APISIX。单机模式: 支持从本地配置文件中加载路由规则,在 kubernetes(k8s) 等环境下更友好。全局规则:允许对所有请求执行插件,比如黑白名单、限流限速等。高性能:在单核上 QPS 可以达到 18k,同时延迟只有 0.2 毫秒。故障注入REST Admin API: 使用 REST Admin API 来控制 Apache APISIX,默认只允许 127.0.0.1 访问,你可以修改 conf/config.yaml 中的 allow_admin 字段,指定允许调用 Admin API 的 IP 列表。同时需要注意的是,Admin API 使用 key auth 来校验调用者身份,在部署前需要修改 conf/config.yaml 中的 admin_key 字段,来保证安全。外部日志记录器:将访问日志导出到外部日志管理工具。(HTTP Logger, TCP Logger, Kafka Logger, UDP Logger)Helm charts高度可扩展自定义插件: 允许挂载常见阶段,例如init, rewrite,access,balancer,header filter,body filter 和 log 阶段。插件可以用 Java/Go 编写自定义负载均衡算法:可以在 balancer 阶段使用自定义负载均衡算法。自定义路由: 支持用户自己实现路由算法。项目概述APISIX 是基于 OpenResty 开发的 API 网关,与 OpenResty 的请求生命周期一致,APISIX 利用 Lua Nginx Module 提供的 *_by_lua 添加 Hook。APISIX 抽象了 Route、Service、Upstream、Plugin、Consumer 等数据模型,与 Kong 网关如出一辙。基本上可以看作 APISIX 是 Kong 网关的重构——运用大量 LuaJIT、OpenResty 技巧优化性能、简化复杂的数据结构、替换储存引擎为 etcd 等。值得一提的是,在 APISIX 的一个 issue 中,项目开发者说不确定是什么原因,我们看看 Kong 网关是怎么解决的吧。“Kong是如何解决类似问题的?"生态概述Kong 网关开源生态有的,APISIX 基本都有或者正在做。包含:Kubernetes Ingress Controller、Mesh、Dashboard。插件方面比 Kong 开源版本多了 Skywalking APM 数据上报、Traffit 流量拆分、Mirror 流量镜像等功能。基本流程本节概述 APISIX 的目录结构,以及其启动流程。目录结构$ tree -L 2 . ├── apisix │ ├── admin # Admin API │ ├── api_router.lua │ ├── balancer # 负载均衡器 │ ├── balancer.lua │ ├── cli # CLI, Lua 脚本 │ ├── constants.lua # 常量 │ ├── consumer.lua │ ├── control │ ├── core # 主要是封装的公共方法 │ ├── core.lua │ ├── debug.lua │ ├── discovery # 服务发现, 支持 consul, eruka, dns │ ├── http │ ├── init.lua # _by_lua 函数入口 │ ├── patch.lua │ ├── plugin_config.lua │ ├── plugin.lua # 插件 │ ├── plugins │ ├── router.lua # Router │ ├── schema_def.lua # jsonschema 定义 │ ├── script.lua │ ├── ssl │ ├── ssl.lua │ ├── stream │ ├── timers.lua # timer 封装 │ ├── upstream.lua │ └── utils ├── bin │ └── apisix # apisix CLI, shell 脚本 ├── ci # CI 脚本 ├── conf # 默认配置文件 ├── deps ├── docs ├── Makefile # 快捷指令 ├── rockspec # luarocks 包管理 ├── t # Test::Nginx 测试 └── utils # Shell 脚本启动流程CLI 默认会用 LuaJIT 启动,若版本不够便退回到 Lua 5.1 解释器执行。# 查找 APISIX LUA 包路径 # shell -s 判断文件是否存在且 size > 0 # ref: https://stackoverflow.com/questions/53319817/what-is-the-meaning-of-n-z-x-l-d-etc-in-shell-script if [ -s './apisix/cli/apisix.lua' ]; then ... fi # shell -e 判断文件是否存在 if [[ -e $OR_EXEC && "$OR_VER" =~ "1.19" ]]; then # use the luajit of openresty echo "$LUAJIT_BIN $APISIX_LUA $*" exec $LUAJIT_BIN $APISIX_LUA $* elif [[ "$LUA_VERSION" =~ "Lua 5.1" ]]; then # OpenResty version is not 1.19, use Lua 5.1 by default # shell &* 传递所有 args # ref: https://stackoverflow.com/questions/4824590/propagate-all-arguments-in-a-bash-shell-script echo "lua $APISIX_LUA $*" exec lua $APISIX_LUA $* fi启动过程中:调用 popen 执行 CMD 命令;使用 luasocket 库发起 HTTP 请求(非 OpenResty 运行时);使用 ltn12 sink 进行流处理;创建 etcd prefix,value 为 init;基本类型操作基本上为了追求极致性能,能用 FFI 调用实现的都用了。字符串使用 FFI 调用 libc 函数 memcmp 进行字符串比较内存地址的前 n 长度是否相同。local ffi = require("ffi") local C = ffi.C -- ref: https://www.cplusplus.com/reference/cstring/memcmp/ -- ref: https://www.tutorialspoint.com/c_standard_library/c_function_memcmp.htm ffi.cdef[[ int memcmp(const void *s1, const void *s2, size_t n); ]]接收类型是 const void *,不可变类型可以直接传入 Lua string 类型。如果你的 C 函数接受 const char * 或者等价的 const unsigned char/int8_t/... * 这样的参数类型, 可以直接传递 Lua string 进去,而无需另外准备一个 ffi.new 申请的数组。string 前缀比较,比较 s, prefix 内存地址的前 n (#prefix) 长度是否相同。-- 用 ffi 扩展 string 方法 function _M.has_prefix(s, prefix) if type(s) ~= "string" or type(prefix) ~= "string" then error("unexpected type: s:" .. type(s) .. ", prefix:" .. type(prefix)) end if #s < #prefix then return false end -- 比较 s, prefix 内存地址的前 n (#prefix) 长度是否相同 local rc = C.memcmp(s, prefix, #prefix) return rc == 0 end同理比较后缀:C.memcmp(ffi_cast("char *", s) + #s - #suffix, suffix, #suffix)TableTable 是 Lua 中最常用的类型了,与其他语言比较的话相当于 PHP 的 Array 一样实用。Lua Table 需要注意的地方其一:table.new(narray, nhash)这个函数,会预先分配好指定的数组和哈希的空间大小,而不是在插入元素时自增长,这也是它的两个参数 narray 和 nhash 的含义。 如果不使用这个函数,自增长是一个代价比较高的操作,会涉及到空间分配、resize 和 rehash 等,我们应该尽量避免。table.new 的文档并没有出现在 LuaJIT 的官网,而是深藏在 GitHub 项目的 扩展文档 里,用谷歌也很难找到,所以很多人并不知道这个函数的存在。超出预设的空间大小,也可以正常使用,只不过性能会退化,也就失去了使用 table.new 的意义。需要根据实际场景,来预设好 table.new 中数组和哈希空间的大小,这样才能在性能和内存占用上找到一个平衡点。3Lua Table 需要注意的地方其二:table.insert 虽然是一个很常见的操作,但性能并不乐观。 如果不是根据指定下标来插入元素,那么每次都需要调用 LuaJIT 的 lj_tab_len 来获取数组的长度,以便插入队尾。获取 table 长度的时间复杂度为 O(n) 。参考 APISIX 作者给 ingress-nginx 项目提的 Table 操作优化 PR:used table functions of LuaJIT for better performance.OpenResty Fork 的 LuaJIT 新增的 table 函数4:table.isemptytable.isarraytable.nkeystable.clone回到 APISIX 封装的 Table 操作符:-- 自行构建 index 插入 table, 比 table.insert 效率高 function _M.insert_tail(tab, ...) local idx = #tab -- 遍历输入的参数 for i = 1, select('#', ...) do idx = idx + 1 tab[idx] = select(i, ...) end return idx endselect('#', ...) 获取输入参数的数量,select(i, ...) 获取第 n 个参数,Table 的遍历中大量使用该结构。try_read_attr 实现了 path.node.x 的 table 访问方式,便于读取多层级配置项。function _M.try_read_attr(tab, ...) for i = 1, select('#', ...) do local attr = select(i, ...) if type(tab) ~= "table" then return nil end tab = tab[attr] end return tab end使用示例: local size = core_tab.try_read_attr(local_conf, "graphql", "max_size") if size then max_size = size end工具类APISIX 封装了许多工具类,这些工具共同组成了 APISIX 的 PDK(Plugin Development Kit),利用这些方法,插件开发能够增速许多。JSON 操作local delay_tab = setmetatable({data = "", force = false}, { __tostring = function(self) local res, err = encode(self.data, self.force) if not res then ngx.log(ngx.WARN, "failed to encode: ", err, " force: ", self.force) end return res end }) -- this is a non-thread safe implementation -- it works well with log, eg: log.info(..., json.delay_encode({...})) function _M.delay_encode(data, force) delay_tab.data = data delay_tab.force = force return delay_tab end设置了元表的 __tostring 方法,在字符串转换时才使用匿名函数调用 json.encode,在日志打印时,被忽略的日志会不执行 JSON 压缩,避免额外的性能损耗。LRU 缓存lua-resty-lrucache 在写入时会清理 TTL 过期的缓存,读时如果数据过期了,会作为第二个参数返回:function _M.get(self, key) local hasht = self.hasht local val = hasht[key] if val == nil then return nil end local node = self.key2node[key] -- print(key, ": moving node ", tostring(node), " to cache queue head") local cache_queue = self.cache_queue queue_remove(node) queue_insert_head(cache_queue, node) if node.expire >= 0 and node.expire < ngx_now() then -- print("expired: ", node.expire, " > ", ngx_now()) return nil, val, node.user_flags end return val, nil, node.user_flags endlocal function fetch_valid_cache(lru_obj, invalid_stale, item_ttl, item_release, key, version) local obj, stale_obj = lru_obj:get(key) if obj and obj.ver == version then return obj end -- 如果 TTL 到期的数据版本号仍一致, 重新 set 该缓存 if not invalid_stale and stale_obj and stale_obj.ver == version then lru_obj:set(key, stale_obj, item_ttl) return stale_obj end -- release 回调 if item_release and obj then item_release(obj.val) end return nil end -- 返回创建 LRU 的匿名函数 local function new_lru_fun(opts) local item_count, item_ttl if opts and opts.type == 'plugin' then item_count = opts.count or PLUGIN_ITEMS_COUNT item_ttl = opts.ttl or PLUGIN_TTL else item_count = opts and opts.count or GLOBAL_ITEMS_COUNT item_ttl = opts and opts.ttl or GLOBAL_TTL end local item_release = opts and opts.release local invalid_stale = opts and opts.invalid_stale -- 是否使用并发锁 local serial_creating = opts and opts.serial_creating -- 参数为 LRU size local lru_obj = lru_new(item_count) return function (key, version, create_obj_fun, ...) -- 不支持的 yielding 的 Nginx phase 无法使用 resty.lock if not serial_creating or not can_yield_phases[get_phase()] then local cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl, item_release, key, version) if cache_obj then return cache_obj.val end local obj, err = create_obj_fun(...) if obj ~= nil then lru_obj:set(key, {val = obj, ver = version}, item_ttl) end return obj, err end local cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl, item_release, key, version) if cache_obj then return cache_obj.val end -- 当缓存失效时获取锁 -- 创建共享内存 lock local lock, err = resty_lock:new(lock_shdict_name) if not lock then return nil, "failed to create lock: " .. err end local key_s = tostring(key) log.info("try to lock with key ", key_s) -- 获取 lock local elapsed, err = lock:lock(key_s) if not elapsed then return nil, "failed to acquire the lock: " .. err end -- 再次获取缓存 cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl, nil, key, version) if cache_obj then lock:unlock() log.info("unlock with key ", key_s) return cache_obj.val end local obj, err = create_obj_fun(...) if obj ~= nil then lru_obj:set(key, {val = obj, ver = version}, item_ttl) end lock:unlock() log.info("unlock with key ", key_s) return obj, err end end这段代码关联到两个 PR:bugfix(lrucache): when creating cached objects, use resty-lock to avoid repeated creation.change: make lrucache lock optional使用 lua-resty-lock 通过共享内存竞争锁,用在缓存中避免缓存击穿,当该 Lib 出于 Luajit 限制,无法在 init_by_lua, init_worker_by_lua, header_filter_by_lua, body_filter_by_lua, balancer_by_lua, log_by_lua阶段中使用。引入的 serial_creating 属性用于判断插件是否需要启用锁。Kong 使用的 lua-resty-mlcache 库内部也使用 resty.lock 防止缓存击穿(可选)。后台任务两个地方默认初始化了定时器(Nginx Timer)执行后台任务。init_by_lua 阶段创建 OpenResty 特权进程,负责执行特定的后台任务,不会干扰其他 Worker 进程,权限相当于 root;init_by_worker 阶段创建 Background Timer,执行并发执行后台任务。OpenResty 特权进程不能处理请求,只能由 Timer 触发,逻辑上编写 if type(ngx.process.type()) == "privileged agent" 只在特权进程中执行操作。5Enables the privileged agent process in Nginx.The privileged agent process does not listen on any virtual server ports like those worker processes. And it uses the same system account as the nginx master process, which is usually a privileged account like root.The init_worker_by_lua* directive handler still runs in the privileged agent process. And one can use the type function provided by this module to check if the current process is a privileged agent.6-- worker 默认后台运行的 timer, 执行各种后台任务 local function background_timer() if core.table.nkeys(timers) == 0 then return end local threads = {} for name, timer in pairs(timers) do core.log.info("run timer[", name, "]") -- 开启协程执行 local th, err = thread_spawn(timer) if not th then core.log.error("failed to spawn thread for timer [", name, "]: ", err) goto continue end core.table.insert(threads, th) ::continue:: end local ok, err = thread_wait(unpack(threads)) if not ok then core.log.error("failed to wait threads: ", err) end end function _M.init_worker() local opts = { each_ttl = 0, sleep_succ = 0, check_interval = check_interval, -- 默认间隔为 1 秒 } local timer, err = core.timer.new("background", background_timer, opts) if not timer then core.log.error("failed to create background timer: ", err) return end core.log.notice("succeed to create background timer") endAPISIX 引入特权进程的一个目的在于实现 Log Rotate 插件功能。请求生命周期ctxUse ngx.ctx wherever you can. ngx.var is much more expensive and is also limited to string values. The latter should only be used to exchange data with other nginx C modules.7APISIX 中使用缓存 ngx.var 获取的结果, 在不同生命周期中传递。使用 lua-var-nginx-module Nginx C 模块和 FFI 获取变量,在没有开启 Nginx C 模块的情况下回退到 ngx.var 方式获取。APISIX 默认没有在构建脚本中加载 C 模块,提交的 PR feat: add lua-var-nginx-module 在编译 OpenResty 时添加了该模块。function _M.set_vars_meta(ctx) -- 从 table 池中获取/创建一个 hash 长度为 32 的 table local var = tablepool.fetch("ctx_var", 0, 32) if not var._cache then var._cache = {} end -- 通过 resty.core.base 获取原始 request C 指针 (?) -- ref: https://github.com/openresty/lua-resty-core/blob/master/lib/resty/core/base.lua var._request = get_request() -- 绑定元表 setmetatable(var, mt) -- 缓存到 ngx ctx 中 ctx.var = var end使用 tablepool 从 Lua table 池中获取 table,避免频繁分配内存。do -- 获取特殊 var 类型的方法 local var_methods = { method = ngx.req.get_method, -- ref: https://github.com/cloudflare/lua-resty-cookie cookie = function () return ck:new() end } local ngx_var_names = { upstream_scheme = true, upstream_host = true, ... var_x_forwarded_proto = true, } local mt = { -- 重载 hash 元方法 -- t 是 self __index = function(t, key) -- 若 cache table 存在直接返回 local cached = t._cache[key] if cached ~= nil then return cached end if type(key) ~= "string" then error("invalid argument, expect string value", 2) end local val -- 如果是特殊类型, 使用特定方法获取 local method = var_methods[key] if method then val = method() elseif core_str.has_prefix(key, "cookie_") then -- 通过 var_methods 访问到 resty.cookie local cookie = t.cookie if cookie then local err val, err = cookie:get(sub_str(key, 8)) if not val then log.warn("failed to fetch cookie value by key: ", key, " error: ", err) end end elseif core_str.has_prefix(key, "http_") then key = key:lower() key = re_gsub(key, "-", "_", "jo") -- 最终通过 ngx.var 获取 val = get_var(key, t._request) elseif core_str.has_prefix(key, "graphql_") then -- trim the "graphql_" prefix key = sub_str(key, 9) val = get_parsed_graphql(t)[key] elseif key == "route_id" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.route_id elseif key == "service_id" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.service_id elseif key == "consumer_name" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.consumer_name elseif key == "route_name" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.route_name elseif key == "service_name" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.service_name elseif key == "balancer_ip" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.balancer_ip elseif key == "balancer_port" then val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.balancer_port else val = get_var(key, t._request) end if val ~= nil then t._cache[key] = val end -- 为空返回 nil return val end, __newindex = function(t, key, val) if ngx_var_names[key] then ngx_var[key] = val end -- log.info("key: ", key, " new val: ", val) t._cache[key] = val end, }部分 APISIX 路由匹配的内部参数在其他阶段注入。-- 用 ngx.ctx table 缓存 headers, 避免再进行一次 ffi 调用 local function _headers(ctx) if not ctx then ctx = ngx.ctx.api_ctx end local headers = ctx.headers if not headers then headers = get_headers() ctx.headers = headers end return headers end用到了上述的 ctx 库。etcdetcd 在 APISIX 中作用相同与 PostgreSQL 之于 Kong,内部使用 lua-resty-etcd 作为客户端,使用 timer 定时执行和长轮询获取跟踪 etcd 中数据的变化。这里的优化点与 Kong 一样,在 init_by_lua 阶段进行数据的 warm up,之后数据会 fork 到其他的进程中。It does not really make much sense to use this library in the context of init_by_lua because the cache will not get shared by any of the worker processes (unless you just want to “warm up” the cache with predefined items which will get inherited by the workers via fork()).8初始化读取 etcd 数据到全局单例的 Lua table。-- 初始化 etcd-- 初始化 etcd function _M.init() local local_conf, err = config_local.local_conf() if not local_conf then return nil, err end if table.try_read_attr(local_conf, "apisix", "disable_sync_configuration_during_start") then return true end -- 获取 etcd cli local etcd_cli, err = get_etcd() if not etcd_cli then return nil, "failed to start a etcd instance: " .. err end local etcd_conf = local_conf.etcd local prefix = etcd_conf.prefix -- 加载 etcd 所有数据到 lua table 中, 单例模式 local res, err = readdir(etcd_cli, prefix, create_formatter(prefix)) if not res then return nil, err end return true end对数据进行格式化,存入 Lua table 中:-- 创建格式化 formatter local function create_formatter(prefix) -- 返回闭包函数, 对 etcd 返回的结果进行格式化 -- 格式个毛, 这就是个 hook 函数 return function (res) res.body.nodes = {} local dirs if is_http then dirs = constants.HTTP_ETCD_DIRECTORY else dirs = constants.STREAM_ETCD_DIRECTORY end local curr_dir_data local curr_key for _, item in ipairs(res.body.kvs) do if curr_dir_data then -- 将匹配的内容插入 table if core_str.has_prefix(item.key, curr_key) then table.insert(curr_dir_data, etcd_apisix.kvs_to_node(item)) goto CONTINUE end curr_dir_data = nil end -- 截取 prefix 后的 key local key = sub_str(item.key, #prefix + 1) if dirs[key] then -- single item loaded_configuration[key] = { body = etcd_apisix.kvs_to_node(item), headers = res.headers, } else -- 前缀一致 local key = sub_str(item.key, #prefix + 1, #item.key - 1) -- 去掉末尾的 / -- ensure the same key hasn't been handled as single item if dirs[key] and not loaded_configuration[key] then loaded_configuration[key] = { body = { nodes = {}, }, headers = res.headers, } curr_dir_data = loaded_configuration[key].body.nodes curr_key = item.key end end ::CONTINUE:: end return res end end这部分逻辑在 init_by_lua 执行,fork 到其他子进程。数据校验schema_def.lua 文件中定义了所有储存数据结构的 schema 校验规则,使用 jsonschema 库进行数据校验。core/schema.lua 中使用 LRU 缓存校验器。load_full_data 函数加载数据结构所需的 etcd kvs,并进行数据转换、校验、格式化、执行回调。local function load_full_data(self, dir_res, headers) local err local changed = false if self.single_item then -- table size 为 1 ... -- 执行逻辑与下面数组格式类似 else if not dir_res.nodes then dir_res.nodes = {} end self.values = new_tab(#dir_res.nodes, 0) self.values_hash = new_tab(0, #dir_res.nodes) for _, item in ipairs(dir_res.nodes) do local key = short_key(self, item.key) local data_valid = true -- 数据格式校验... -- schema 校验... -- 过滤器... if data_valid then changed = true insert_tab(self.values, item) self.values_hash[key] = #self.values item.value.id = key item.clean_handlers = {} -- 执行回调 if self.filter then self.filter(item) end end -- 更新 mvcc 版本 self:upgrade_version(item.modifiedIndex) end end ... self.need_reload = false end后台数据同步利用 etcd watch 机制进行数据变更的同步。-- 定时器自动同步 etcd 数据-- 定时器自动同步 etcd 数据 local function _automatic_fetch(premature, self) if premature then return end local i = 0 while not exiting() and self.running and i <= 32 do i = i + 1 local ok, err = xpcall(function() if not self.etcd_cli then local etcd_cli, err = get_etcd() if not etcd_cli then error("failed to create etcd instance for key [" .. self.key .. "]: " .. (err or "unknown")) end self.etcd_cli = etcd_cli end -- 同步数据 local ok, err = sync_data(self) if err then if err ~= "timeout" and err ~= "Key not found" and self.last_err ~= err then log.error("failed to fetch data from etcd: ", err, ", ", tostring(self)) end if err ~= self.last_err then self.last_err = err self.last_err_time = ngx_time() else if ngx_time() - self.last_err_time >= 30 then self.last_err = nil end end ngx_sleep(self.resync_delay + rand() * 0.5 * self.resync_delay) elseif not ok then -- no error. reentry the sync with different state ngx_sleep(0.05) end end, debug.traceback) if not ok then log.error("failed to fetch data from etcd: ", err, ", ", tostring(self)) ngx_sleep(self.resync_delay + rand() * 0.5 * self.resync_delay) break end end -- 进行下一次循环 if not exiting() and self.running then ngx_timer_at(0, _automatic_fetch, self) end end配置同步封装上述的逻辑提供给 routes、plugins、services 等数据结构使用,每个数据结构监听自己的 prefix,同步数据并执行回调,通常在回调逻辑上触发更新,例如重新构建 Router、重新构建 plugins table 等。-- etcd 配置创建 function _M.new(key, opts) local local_conf, err = config_local.local_conf() if not local_conf then return nil, err end -- etcd 重新同步事件 5 秒, 与 Kong 重新 poll db 数据一致 local etcd_conf = local_conf.etcd local prefix = etcd_conf.prefix local resync_delay = etcd_conf.resync_delay if not resync_delay or resync_delay < 0 then resync_delay = 5 end local automatic = opts and opts.automatic local item_schema = opts and opts.item_schema local filter_fun = opts and opts.filter local timeout = opts and opts.timeout local single_item = opts and opts.single_item local checker = opts and opts.checker local obj = setmetatable({ etcd_cli = nil, key = key and prefix .. key, automatic = automatic, item_schema = item_schema, checker = checker, sync_times = 0, running = true, conf_version = 0, values = nil, need_reload = true, routes_hash = nil, prev_index = 0, last_err = nil, last_err_time = nil, resync_delay = resync_delay, timeout = timeout, single_item = single_item, filter = filter_fun, }, mt) if automatic then -- timer 定时获取数据 if not key then return nil, "missing `key` argument" end -- 从单例 table 获取 etcd 数据, 进行处理 if loaded_configuration[key] then local res = loaded_configuration[key] -- 清空 table loaded_configuration[key] = nil -- tried to load log.notice("use loaded configuration ", key) local dir_res, headers = res.body, res.headers -- 加载数据并校验数据, 过滤数据 load_full_data(obj, dir_res, headers) end -- 创建定时器自动同步 ngx_timer_at(0, _automatic_fetch, obj) else local etcd_cli, err = get_etcd() if not etcd_cli then return nil, "failed to start a etcd instance: " .. err end obj.etcd_cli = etcd_cli end if key then created_obj[key] = obj end return obj endRouterAPISIX 的 Router 匹配基于压缩字典树(Radix Tree)实现,主要使用 lua-resty-radixtree 库。内置多种解析模式,这里只关注 HTTP 默认的 radixtree_uri 实现。路由构建core.config.new 调用的是 etcd 库(config_etcd.lua)维护的配置同步方法,返回原表,可以访问从 etcd 同步的数据。core.schema.route 包含了 route 这个数据结构的 schema 及校验规则,check_route 内部检查 route 直接绑定 plugin 的数据结构。APISIX 引入 route 直接绑定 plugin 的简化配置,不需要额外创建 plugin 对象。-- 初始化 router function _M.init_worker(filter) local user_routes, err = core.config.new("/routes", { automatic = true, -- 自动同步 item_schema = core.schema.route, checker = check_route, filter = filter, }) if not user_routes then error("failed to create etcd instance for fetching /routes : " .. err) end return user_routes endfilter 是回调函数,下述的流程中会注入。路由初始化router.http_init_worker 中进行 Router 初始化。-- attach common methods if the router doesn't provide its custom implementation local function attach_http_router_common_methods(http_router) ... if http_router.init_worker == nil then http_router.init_worker = function (filter) -- 添加路由 http_router.user_routes = http_route.init_worker(filter) end end end function _M.http_init_worker() local conf = core.config.local_conf() -- 默认的匹配模式 local router_http_name = "radixtree_uri" local router_ssl_name = "radixtree_sni" if conf and conf.apisix and conf.apisix.router then router_http_name = conf.apisix.router.http or router_http_name router_ssl_name = conf.apisix.router.ssl or router_ssl_name end -- 创建 router 实例 local router_http = require("apisix.http.router." .. router_http_name) -- 修改 router 的 table attach_http_router_common_methods(router_http) -- 初始化路由 -- 调用 apisix.http.route.init_worker 方法 -- 从 etcd 获取数据并执行回调 -- filter 为格式化, 解析 upstream router_http.init_worker(filter) _M.router_http = router_http local router_ssl = require("apisix.ssl.router." .. router_ssl_name) router_ssl.init_worker() _M.router_ssl = router_ssl _M.api = require("apisix.api_router") ... endhttp_router.user_routes 储存在 router 的 table 中,会在路由匹配时用到(懒加载)。路由匹配access_by_lua 阶段中进行路由匹配,将匹配结果(route、service)传递到 ctx 中供 balancer 请求上游。do local uri_routes = {} local uri_router local match_opts = {} function _M.match(api_ctx) -- 从 module 的 user_routes 属性获取路由, 在 etcd route 变化时回调添加 local user_routes = _M.user_routes if not cached_version or cached_version ~= user_routes.conf_version then uri_router = base_router.create_radixtree_uri_router(user_routes.values, uri_routes, false) cached_version = user_routes.conf_version end if not uri_router then core.log.error("failed to fetch valid `uri` router: ") return true end return base_router.match_uri(uri_router, match_opts, api_ctx) end endradixtree 路由匹配库提供了匹配成功回调 handler,匹配成功后传递到 ctx 中。core.table.insert(uri_routes, { ... handler = function (api_ctx, match_opts) api_ctx.matched_params = nil api_ctx.matched_route = route api_ctx.curr_req_matched = match_opts.matched end })BalancerBalancer 部分与 Kong 逻辑一致,甚至代码里函数名都一样,主要逻辑是 Service/Upstream 节点解析、负载均衡策略、健康检查与失败重试。APISIX 支持的一特性是外部服务发现,Kong 中默认支持通过 DNS 解析 Service host,根据 AAAA、A、SRV 记录添加 IP 与优先级,APISIX 支持了从 consul、eruka 和其他注册中心获取 IP 地址列表,并同步节点数据(长轮询)。服务发现如果 serivce host 是域名, 通过外部注册中心进行服务发现,获取上游 IP 列表。function _M.set_by_route(route, api_ctx) ... -- 如果 serivce host 是域名, 通过 discovery 发现, dns 解析 if up_conf.service_name then ... -- 外部注册中心 local dis = discovery[up_conf.discovery_type] if not dis then return 500, "discovery " .. up_conf.discovery_type .. " is uninitialized" end -- 从注册中心数据源(缓存本地 table)获取 IP local new_nodes, err = dis.nodes(up_conf.service_name) if not new_nodes then return HTTP_CODE_UPSTREAM_UNAVAILABLE, "no valid upstream node: " .. (err or "nil") end ... end -- 将 upstream 节点信息存入 ctx set_directly(api_ctx, up_conf.type .. "#upstream_" .. tostring(up_conf), api_ctx.conf_version, up_conf) local nodes_count = up_conf.nodes and #up_conf.nodes or 0 if nodes_count == 0 then return HTTP_CODE_UPSTREAM_UNAVAILABLE, "no valid upstream node" end ... set_upstream_scheme(api_ctx, up_conf) local ok, err = fill_node_info(up_conf, api_ctx.upstream_scheme, false) if not ok then return 503, err end ... local scheme = up_conf.scheme if (scheme == "https" or scheme == "grpcs") and up_conf.tls then ... end return end负载均衡不同于 Kong 使用自己封装的 lua-resty-dns-client/balancer 作为负载均衡器,APISIX 基于 lua-resty-balancer 封装了负载均衡策略,基于 lua-resty-healthcheck(fork 版本)实现节点健康检查。API 网关的负载均衡策略(Kong/APISIX)都是基于 OpenResty lua-resty-core/balancer 提供的负载均衡函数实现,set_current_peer 设置当前请求上游地址,set_more_tries 设置请求失败重试次数,get_last_failure 获取上一次请求失败结果判断是否需要继续重试,set_timeouts 设置单个请求超时时间。set_balancer_opts 设置 Nginx Balancer 参数。-- set_balancer_opts will be called in balancer phase and before any tries local function set_balancer_opts(route, ctx) local up_conf = ctx.upstream_conf -- If the matched route has timeout config, prefer to use the route config. local timeout = nil if route and route.value and route.value.timeout then timeout = route.value.timeout else if up_conf.timeout then timeout = up_conf.timeout end end -- 设置 Nginx 请求超时时间 if timeout then local ok, err = set_timeouts(timeout.connect, timeout.send, timeout.read) if not ok then core.log.error("could not set upstream timeouts: ", err) end end local retries = up_conf.retries if not retries or retries < 0 then retries = #up_conf.nodes - 1 end -- 设置 Nginx 失败重试次数 if retries > 0 then local ok, err = set_more_tries(retries) ... end end在 access_by_lua 阶段中服务发现,调用 balancer 库获取 peer 节点,balancer_by_lua 中从 ctx 中获取 peer 节点信息,访问后端节点,若失败重试(该阶段再次被调用),重新获取 peer 节点,重新创建请求(recreate_request())再次访问后端节点。Plugin插件机制也与 Kong 类似,插件开发者可以定义 Schema 配置数据结构,以及 Handler 注入 Nginx 请求生命周期,API 网关提供核心的库供开发者使用(SDK)。APISIX 相比 Kong,开源的插件较多,插件 Schema 便于编写,同时插件只需要单文件,而 Kong 的插件通常是单独一个仓库,不方便维护。但是考虑到插件需要单独的 Test::Nginx 单元测试,单独一个仓库也未尝不可(Kong 还说了以后会把 Github 项目主仓库的插件代码移到单独的仓库)。具体各个阶段执行逻辑应该与 Kong 相同,即部分阶段插件开协程并发执行,部分阶段避免数据竞争,插件顺序执行。值得注意的一点是 APISIX 生命周期里没有 rewrite_by_lua 阶段,插件实现的该阶段会在 access_by_lua 中优先于 access_by_lua 插件逻辑执行。The apisix run both “.access” and “.rewrite” in the “access” phase.9插件加载插件列表从本地 yaml 文件获取,同时监听本地文件变化,同步配置;插件配置信息从 etcd 获取。local init_plugins_syncer do local plugins_conf function init_plugins_syncer() local err -- 储存插件的配置信息, 一条 kv plugins_conf, err = core.config.new("/plugins", { automatic = true, -- 后台创建 timer watch etcd 自动同步配置 item_schema = core.schema.plugins, single_item = true, -- filter 方法中访问到 etcd kv 的 item, 这里进行插件加载的回调 -- 每次 etcd 插件配置变动, 自动同步 filter = function(item) -- we need to pass 'item' instead of plugins_conf because -- the latter one is nil at the first run _M.load(item) end, }) if not plugins_conf then error("failed to create etcd instance for fetching /plugins : " .. err) end end end插件列表会储存到 Lua table 中:-- 加载插件 local function load(plugin_names) local processed = {} for _, name in ipairs(plugin_names) do if processed[name] == nil then processed[name] = true end end core.log.warn("new plugins: ", core.json.delay_encode(processed)) -- 移除已经存在的 module for name in pairs(local_plugins_hash) do unload_plugin(name) end core.table.clear(local_plugins) core.table.clear(local_plugins_hash) -- 加载插件 for name in pairs(processed) do load_plugin(name, local_plugins) end -- 插件排序, priority 越高的插件越先执行, 与 Kong 同样 -- sort by plugin's priority if #local_plugins > 1 then sort_tab(local_plugins, sort_plugin) end -- 打印调试日志 for i, plugin in ipairs(local_plugins) do ... end return true end插件配置信息 plugin_meta 也加载到 Lua table 中,在插件匹配的时候会获取。插件匹配插件过滤,遍历插件列表,匹配开启的插件,O(n) 操作 plugin.filter(route) :-- 插件配置绑定 function _M.filter(user_route, plugins) ... plugins = plugins or core.tablepool.fetch("plugins", 32, 0) for _, plugin_obj in ipairs(local_plugins) do local name = plugin_obj.name local plugin_conf = user_plugin_conf[name] -- 插件和插件配置存入 if type(plugin_conf) == "table" and not plugin_conf.disable then core.table.insert(plugins, plugin_obj) core.table.insert(plugins, plugin_conf) end end trace_plugins_info_for_debug(plugins) return plugins end插件执行这里以 access_by_lua 阶段插件执行逻辑为例,根据 Route、Service 匹配插件,创建临时 Table 储存 plugin 和 plugin_conf,存入 ctx 中。 -- 插件过滤, 遍历插件列表, 匹配开启的插件, O(n) local plugins = plugin.filter(route) api_ctx.plugins = plugins -- fake 执行 rewrite 阶段 plugin.run_plugin("rewrite", plugins, api_ctx) if api_ctx.consumer then local changed route, changed = plugin.merge_consumer_route( route, api_ctx.consumer, api_ctx ) core.log.info("find consumer ", api_ctx.consumer.username, ", config changed: ", changed) if changed then core.table.clear(api_ctx.plugins) api_ctx.plugins = plugin.filter(route, api_ctx.plugins) end end -- 执行 access 阶段 plugin.run_plugin("access", plugins, api_ctx) 主流程以 Nginx HTTP Subsystem 为例分析主要执行逻辑,其中一些核心逻辑已在上述小节中流程分析过。init_by_luafunction _M.http_init(args) require("resty.core") if require("ffi").os == "Linux" then require("ngx.re").opt("jit_stack_size", 200 * 1024) end require("jit.opt").start("minstitch=2", "maxtrace=4000", "maxrecord=8000", "sizemcode=64", "maxmcode=4000", "maxirconst=1000") core.resolver.init_resolver(args) -- 生成节点 ID core.id.init() -- 启用 openresty 的特权进程 local process = require("ngx.process") local ok, err = process.enable_privileged_agent() if not ok then core.log.error("failed to enable privileged_agent: ", err) end -- 从 etcd / yaml 本地配置文件获取配置, etcd 有 init 函数 if core.config.init then local ok, err = core.config.init() if not ok then core.log.error("failed to load the configuration: ", err) end end endinit_worker_by_luafunction _M.http_init_worker() local seed, err = core.utils.get_seed_from_urandom() if not seed then core.log.warn('failed to get seed from urandom: ', err) seed = ngx_now() * 1000 + ngx.worker.pid() end math.randomseed(seed) -- for testing only core.log.info("random test in [1, 10000]: ", math.random(1, 10000)) -- 进程间事件通信 local we = require("resty.worker.events") local ok, err = we.configure({shm = "worker-events", interval = 0.1}) if not ok then error("failed to init worker event: " .. err) end -- 服务发现 lib local discovery = require("apisix.discovery.init").discovery -- 默认没有开启服务发现 if discovery and discovery.init_worker then discovery.init_worker() end -- 初始化负载均衡器, 方法为空 require("apisix.balancer").init_worker() -- 负载均衡器 load_balancer = require("apisix.balancer") -- TODO admin 流程分析 require("apisix.admin.init").init_worker() -- 注册全局 timer require("apisix.timers").init_worker() -- 加载所有插件并执行插件 init plugin.init_worker() -- 初始化 router, 并加载 routes router.http_init_worker() -- 初始化 services, 加载 services require("apisix.http.service").init_worker() -- 加载插件配置 plugin_config.init_worker() -- consumer 加载 require("apisix.consumer").init_worker() if core.config == require("apisix.core.config_yaml") then core.config.init_worker() end require("apisix.debug").init_worker() -- upstreams 加载 apisix_upstream.init_worker() require("apisix.plugins.ext-plugin.init").init_worker() local_conf = core.config.local_conf() if local_conf.apisix and local_conf.apisix.enable_server_tokens == false then ver_header = "APISIX" end endaccess_by_lua-- access_by_lua 阶段, apisix 没有 rewrite_by_lua -- ref: https://github.com/apache/apisix/issues/1120 -- ref: https://github.com/apache/apisix/issues/1120#issuecomment-584949073 function _M.http_access_phase() local ngx_ctx = ngx.ctx ... -- 从 table 缓存池中获取 table -- always fetch table from the table pool, we don't need a reused api_ctx local api_ctx = core.tablepool.fetch("api_ctx", 0, 32) -- 将 table 储存在 ngx.ctx 中, 下一个阶段共享 ngx_ctx.api_ctx = api_ctx -- 绑定 metatable core.ctx.set_vars_meta(api_ctx) ... -- router 路由匹配 router.router_http.match(api_ctx) -- run global rule plugin.run_global_rules(api_ctx, router.global_rules, nil) ... local enable_websocket = route.value.enable_websocket -- route 插件配置绑定 if route.value.plugin_config_id then ... route = plugin_config.merge(route, conf) end -- 获取对应的 service if route.value.service_id then local service = service_fetch(route.value.service_id) ... if enable_websocket == nil then enable_websocket = service.value.enable_websocket end else ... end api_ctx.route_id = route.value.id api_ctx.route_name = route.value.name -- 执行 script if route.value.script then script.load(route, api_ctx) script.run("access", api_ctx) else -- 插件过滤, 遍历插件列表, 匹配开启的插件, O(n) local plugins = plugin.filter(route) api_ctx.plugins = plugins -- fake 执行 rewrite 阶段 plugin.run_plugin("rewrite", plugins, api_ctx) if api_ctx.consumer then local changed route, changed = plugin.merge_consumer_route( route, api_ctx.consumer, api_ctx ) core.log.info("find consumer ", api_ctx.consumer.username, ", config changed: ", changed) if changed then core.table.clear(api_ctx.plugins) api_ctx.plugins = plugin.filter(route, api_ctx.plugins) end end -- 执行 access 阶段 plugin.run_plugin("access", plugins, api_ctx) end local up_id = route.value.upstream_id -- used for the traffic-split plugin if api_ctx.upstream_id then up_id = api_ctx.upstream_id end ... -- websocket 特殊处理 if enable_websocket then api_ctx.var.upstream_upgrade = api_ctx.var.http_upgrade api_ctx.var.upstream_connection = api_ctx.var.http_connection core.log.info("enabled websocket for route: ", route.value.id) end if route.value.service_protocol == "grpc" then api_ctx.upstream_scheme = "grpc" end -- 获取 upstream 节点 local code, err = set_upstream(route, api_ctx) if code then core.log.error("failed to set upstream: ", err) core.response.exit(code) end -- 负载均衡 local server, err = load_balancer.pick_server(route, api_ctx) if not server then core.log.error("failed to pick server: ", err) return core.response.exit(502) end api_ctx.picked_server = server set_upstream_headers(api_ctx, server) -- stash ngx ctx 这部分与 Kong 一致, 怀疑是抄来的(95% 置信区间) ngx_var.ctx_ref = ctxdump.stash_ngx_ctx() local up_scheme = api_ctx.upstream_scheme if up_scheme == "grpcs" or up_scheme == "grpc" then return ngx.exec("@grpc_pass") end if api_ctx.dubbo_proxy_enabled then return ngx.exec("@dubbo_pass") end end一些思考边缘计算对于互联网设备,网络边缘是设备或包含设备的本地网络与互联网通信的位置。边缘是个比较模糊的术语。例如,可以将用户的计算机或 IoT 摄像头内部的处理器视为网络边缘,但也可以将用户的路由器、ISP 或本地边缘服务器视为边缘。重要的是,网络边缘在地理位置上靠近设备,与源站和云服务器不同,后者可能与它们相互通信的设备相距很远。完全减轻额外硬件需求的一种方法是利用边缘服务器。例如,借助 Cloudflare 分散在全球各地的 194 个边缘服务器网络,Cloudflare 的客户可以使用 Cloudflare Workers 在全球范围内运行边缘代码。10Cloudflare 的边缘计算是基于 Edge Gateway(边缘网关、边缘集群)的 Serverless 代码执行,提供了 JS 代码执行,以及 WASM 二进制。11一些相关的 Issue:Support wasm in openresty?feature: support WebAssembly in apisix.ServerlessAPISIX 的 Serverless 插件功能支持注入任何 Lua 脚本,而 Kong 网关也有类似的插件功能。Serverless 插件支持执行简单的函数方法。WebAssemblyAPISIX 自 2019 年发起提案,试图通过 WebAssembly 来扩展 Lua 贫乏的生态。 2021 年,在 WebAssembly 运行时的技术选型上,APISIX 的技术团队更偏向使用由 Fastly 团队 支撑13的 wasmtime 项目。开源的 WebAssembly 除了 wasmtime 还有14:WasmEdge(前身 SSVM),由 Second State 开源的 CNCF 沙箱项目。Wasmer,Dart 语言使用的 Wasm 运行时。Lucet,由 Fastly 开源的 Bytecode Alliance 的 项目,将会与 wasmtime 合并。在 Issue #157 的讨论中,Wasmer 的 CEO 也来插了一嘴, 希望 APISIX 能够选型 Wasmer 运行时,APISIX 成员给了 Wasmer 一个大大的赞, 最终在 api7/wasm-nginx-module 插件中, 还是使用 wasmtime 运行时实现了对 WebAssembly 的支持。Service MeshAPISIX 的 Service Mesh 项目 api7/apisix-mesh-agent,将 APISIX Proxy 作为 Sidecar 运作在数据平面。通过实现控制平面的接口,接入类似 Istio 或 Kuma(由 Kong 创建捐赠给 CNCF) 的控制平面,形成一套完整的 Service Mesh 方案。 该项目本质上是使用 APISIX 替换了 Istio 中的 Envoy。值得一提的是 Kong 类似的 Service Mesh 项目,叫做 Kong Mesh,目前只提供企业版本。
文章
缓存  ·  负载均衡  ·  Kubernetes  ·  Cloud Native  ·  应用服务中间件  ·  Serverless  ·  API  ·  Apache  ·  nginx  ·  容器
2022-06-15
JSON API免费接口
 各种提供JSON格式数据返回服务网站的API接口 电商接口 京东获取单个商品价格接口: http://p.3.cn/prices/mgets?skuIds=J_商品ID&type=1 用例  ps:商品ID这么获取:http://item.jd.com/954086.html 淘宝商品搜索建议: http://suggest.taobao.com/sug?code=utf-8&q=商品关键字&callback=cb 用例  ps:callback是回调函数设定   物流接口 快递接口: http://www.kuaidi100.com/query?type=快递公司代号&postid=快递单号  ps:快递公司编码:申通="shentong" EMS="ems" 顺丰="shunfeng" 圆通="yuantong" 中通="zhongtong" 韵达="yunda" 天天="tiantian" 汇通="huitongkuaidi" 全峰="quanfengkuaidi" 德邦="debangwuliu" 宅急送="zhaijisong" 谷歌接口 FeedXml转json接口: http://ajax.googleapis.com/ajax/services/feed/load?q=Feed地址&v=1.0 用例(请右击在新窗口打开)官方文档 备选参数:callback:&callback=foo就会在json外面嵌套foo({})方便做jsonp使用。 备选参数:n:返回多少条记录。 百度接口 百度百科接口: http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=关键字&bk_length=600用例(请右击在新窗口打开) 查询出错示例如下:查看原始页面 {"error_code":"20000","error_msg":"search word not found"} 天气接口 百度接口: http://api.map.baidu.com/telematics/v3/weather?location=嘉兴&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ 用例官方文档 location:城市名或经纬度 ak:开发者密钥 output:默认xml 气象局接口: http://m.weather.com.cn/data/101010100.html 解析 用例  新浪接口: http://php.weather.sina.com.cn/iframe/index/w_cl.php?code=js&day=0&city=&dfc=1&charset=utf-8 用例 参数中city如果给了参数就是相关的城市,否则会自动判断day=0的话是今天 返回的参数 大家看着办吧,具体的我也不清楚,新浪没给API。 音乐接口 虾米接口 http://kuang.xiami.com/app/nineteen/search/key/歌曲名称/diandian/1/page/歌曲当前页?_=当前毫秒&callback=getXiamiData 用例 代码解释和下载 QQ空间音乐接口 http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?uin=QQ号码&json=1&g_tk=1916754934用例 代码解释和下载 QQ空间收藏音乐接口 http://qzone-music.qq.com/fcg-bin/fcg_music_fav_getinfo.fcg?dirinfo=0&dirid=1&uin=QQ号&p=0.519638272547262&g_tk=1284234856 多米音乐接口 http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=关键字&pi=页码&pz=每页音乐数 soso接口 http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=关键字&perpage=1&ie=utf-8 视频信息接口JSON在线工具 优酷 http://v.youku.com/player/getPlayList/VideoIDS/视频ID (比如 http://v.youku.com/v_show/id_XNTQxNzc4ODg0.html的ID就是XNTQxNzc4ODg0) 爱奇艺 http://cache.video.iqiyi.com/jp/avlist/202861101/1/?callback=jsonp9 土豆接口 http://api.tudou.com/v3/gw?method=album.item.get&appKey=Appkey&format=json&albumId=视频剧集ID&pageNo=当前页&pageSize=每页显示 示例(火影忍者剧集) 官方文档 http://www.tudou.com/tvp/getMultiTvcCodeByAreaCode.action?type=3&app=4&codes=Lqfme5hSolM&areaCode=320500&jsoncallback=__TVP_getMultiTvcCodeByAreaCode 示例(火影忍者APP剧集) 地图接口 阿里云根据地区名获取经纬度接口 http://gc.ditu.aliyun.com/geocoding?a=苏州市 官方文档 参数解释: 纬度,经度 type 001 (100代表道路,010代表POI,001代表门址,111可以同时显示前三项) 阿里云根据经纬度获取地区名接口 http://gc.ditu.aliyun.com/regeocoding?l=39.938133,116.395739&type=001 官方文档 获取用户的IP,国家代码缩写,经纬度 http://www.telize.com/geoip?callback=a 测试用例 参数解释: callback是回调函数 获取用户经纬度,以及获取附近建筑物名/span> http://ditu.amap.com/service/pl/pl.json?rand=635840524184357321测试用例 http://ditu.amap.com/service/regeo?longitude=121.04925573429551&latitude=31.315590522490712测试用例 IP接口 新浪接口(ip值为空的时候 获取本地的) http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255 淘宝接口 http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42 手机信息查询接口 JSON在线工具 淘宝网接口 http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=手机号 拍拍接口 http://virtual.paipai.com/extinfo/GetMobileProductInfo?mobile=手机号&amount=10000&callname=getPhoneNumInfoExtCallback 用例 百付宝接口 https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=手机号 115接口 http://cz.115.com/?ct=index&ac=get_mobile_local&callback=jsonp1333962541001&mobile=手机号 有道接口 http://www.youdao.com/smartresult-xml/search.s?jsFlag=true&type=mobile&q=手机号 手机在线接口 http://api.showji.com/Locating/www.showji.com.aspx?m=手机号&output=json&callback=querycallback 翻译、词典接口 腾讯 http://dict.qq.com/dict?q=词语 腾讯的部分接口 获取QQ昵称和用户头像 http://r.qzone.qq.com/cgi-bin/user/cgi_personal_card?uin=QQ(不过是jsonp哦) 其他接口 360笑话接口 http://xiaohua.hao.360.cn/m/itxt?page=1&callback=jsonp7  
文章
新零售  ·  JSON  ·  测试技术  ·  API  ·  数据格式
2016-04-11
jquery+asp.net 调用百度geocoder手机浏览器定位--Api介绍及Html定位方法
原文来自:***/projecteactual/jqueryaspnetbaidugeocodermobilebrowserposition.html 在做一个社区项目中,支持移动浏览器进行选择地区和社区,由于地区很多,想在默认的情况下定位手机用户的城市, 方便用户进行配置自己喜欢的社区。 1.选择一个地图服务接口: Google一下,发现百度提供这样的一个接口:Geocoding API .个人推荐百度,因为Google不知道那天就不能调用了。 首先,要借用别人的服务,通过别人的服务接口获取自己想要的数据。Geocoding API 是百度提供的服务接口,主要是用于提供从地址解析到经纬度坐标或者从经纬度坐标解析到地址的转换服务。 供程序员调用的、http形式的地图服务接口。主要服务那些非网页程序的调用。例如C# 、C++、Java等开发语言都能发送http请求且能接收返回数据。 用户只需在请求的url字串中拼接好关键字或者经纬度信息,即可获取到相应的百度经纬度或者结构化地理信息。 Geocoding API有哪些功能? Geocoding API包括地址解析和逆地址解析功能。 地址解析是指,由详细到街道的结构化地址得到百度经纬度信息,且支持名胜古迹、标志性建筑名称直接解析返回百度经纬度。例如:“北京市海淀区中关村南大街27号”地址解析的结果是“lng:116.31985,lat:39.959836”,“百度大厦”地址解析的结果是“lng:116.30815,lat:40.056885” 逆地址解析是指,由百度经纬度信息得到结构化地址信息。例如:“lat:31.325152,lng:120.558957”逆地址解析的结果是“江苏省苏州市虎丘区塔园路318号”。 注意: 1).因为Geocoding和反Geocoding使用的门址数据以及算法都不是一样的,所以会出现不能一一对应的现象。 2).解析过程中可能会出现一对坐标值对应多个地址门牌信息,本接口将返回距离坐标点最近的一个地址门牌信息。 项目地址:http://developer.baidu.com/map/geocoding-api.htm   2.Html5支持获取用户地理位置信息: html5为window.navigator提供了geolocation属性,用于获取基于浏览器的当前用户地理位置。  window.navigator.geolocation提供了3个方法分别是: void getCurrentPosition(onSuccess,onError,options);//获取用户当前位置int watchCurrentPosition(onSuccess,onError,options);//持续获取当前用户位置void clearWatch(watchId);//watchId 为watchCurrentPosition返回的值//取消监控   支持参数://options可以不写,为默认即可 options = {      enableHighAccuracy,//boolean 是否要求高精度的地理信息      timeout,//获取信息的超时限制      maximumAge//对地理信息进行缓存的时间 }   Html 5 geolocation 实例代码如下: $(document).bind("pageinit", function (event, data) {        getLocation(); }); function getLocation() {     if (navigator.geolocation) {             navigator.geolocation.getCurrentPosition(                                  showPosition,//成功回调函数                                 getPositionError,//失败回调函数                                     {//options参数配置                                      enableHighAccuracy:true,//boolean 是否要求高精度的地理信息                                        timeout:2000,                                          maximumAge:36000                                     } );     }     else {  //不支持,就拉倒吧。 } } 失败回调函数: function getPositionError(error){       switch(error.code){           case error.TIMEOUT:             //  alert("连接超时,请重试");               break;           case error.PERMISSION_DENIED:               //alert("您拒绝了使用位置共享服务,查询已取消");               break;           case error.POSITION_UNAVAILABLE:               //alert("亲爱的火星网友,非常抱歉,我们暂时无法为您所在的星球提供位置服务");               break;       }   }   成功回调函数: function showPosition(position) { //内容在下面说。 } 成功返回的结果说明position: 当成功获取地理位置信息时候,onsuccess方法中会返回position对象,通过这个对象可以获取地理位置的相关信息,包括:  position对象的属性: latitude,//纬度 longitude,//经度 altitude,//海拔高度 accuracy,//获取纬度或者经度的精度 altitudeAccurancy,//海拔高度的精度 heading,//设备前景方向。正北方向的顺时针旋转角 speed,//设备的前进速度 m/s timestamp,//获取地理位置信息时候的时间   3.封装对百度Geocoding API的调用 下面是百度给的例子。 根据坐标获取它的地址  http://api.map.baidu.com/geocoder?output=json&location=39.983424,%20116.322987&key=37492c0ee6f924cb5e934fa08c6b1676  //解析“lat:39.983424, lng:116.322987”坐标返回“北京市海淀区中关村大街27号1101-08室”,以json格式输出  http://api.map.baidu.com/geocoder?output=xml&location=39.983424,%20116.322987&key=37492c0ee6f924cb5e934fa08c6b1676  //解析“lat:39.983424, lng:116.322987”坐标返回“北京市海淀区中关村大街27号1101-08室”,以xml格式输出             我一直想用jquery 的$.getJson方式调用,但是一直不成功。肯定是跨域的问题了,尝试了几次都不成功就换了个思路。 用asp.net做个中介吧,通过后台C#实现api的数据读取和分析,再传给前台。效率上差别不是很大,但是心里还有点郁闷。 下面是具体实现了。 1)、为了方便解析百度返回的json结果,我使用了开源代码: Newtonsoft.Json。大家可以到这里看看 Json.NET  http://json.codeplex.com/。 很好用的。解析和烦解析json,可以object to json,也可以反转。 2)、对照百度返回的json结果,我建了一个直接对应的类。 {     "status":"OK",     "result":{         "location":{             "lng":116.322987,             "lat":39.983424         },         "formatted_address":"北京市海淀区中关村大街27号1101-08室",         "business":"人民大学,中关村,苏州街",         "addressComponent":{             "city":"北京市",             "district":"海淀区",             "province":"北京市",             "street":"中关村大街",             "street_number":"27号1101-08室"         },         "cityCode":131     } } 我的类: public class JsonCity {     public JsonCity()     {       }     public string status { get; set; }     public Result result { get; set; }   } public class Result {     public Location location { get; set; }     public string  formatted_address { get; set; }     public AddressComponent addressComponent { get; set; }   } public class Location {     public string  lng { get; set; }     public string  lat { get; set; } } public class AddressComponent  {      public string  city { get; set; }      public string  district { get; set; }      public string  province { get; set; }      public string  street { get; set; }      public string street_number{ get; set; }   } 3)、WebClient 调用百度Api接口,并解析    private string GetCityinfo()     {         WebClient client = new WebClient();//webclient客户端对象         string url = "http://api.map.baidu.com/geocoder?location=" + Jwd + "&output=json&key=c1889c461489a390c023d7e51f23af04";         string cityname = "";          client.Encoding = Encoding.UTF8;//编码格式          string responseTest = client.DownloadString(url);//下载xml响应数据           JsonCity jsonCity = (JsonCity)Newtonsoft.Json.JsonConvert.DeserializeObject(responseTest, typeof(JsonCity));         cityname = jsonCity.result.addressComponent.city;         return cityname;     } 4)、在前端中用js调用我写的这个asp.net页面的方法,实现数据的返回。 function showPosition(position) {     var lat = position.coords.latitude;     var lng = position.coords.longitude;           var AjaxUrl = "Data/City.aspx?tp=4&Jwd=" + lat+"," +lng;      $.ajax({         type: "get",         url: AjaxUrl,         success: function (data, textStatus) {        //成功显示结果。             if (data != null) {               alert(data)             }         },         complete: function (XMLHttpRequest, textStatus) {               //HideLoading();         },         error: function (e) {                   }     }); } 好了,终于是大功告成了。
文章
Web App开发  ·  API  ·  定位技术  ·  C#  ·  数据格式
2013-08-09
...
跳转至:
金融级分布式架构
83 人关注 | 1 讨论 | 259 内容
+ 订阅
  • 深入 HTTP/3(2)|不那么 Boring 的 SSL
  • 蚂蚁集团 Service Mesh 进展回顾与展望
  • SOFA Serverless 体系助力业务极速研发
查看更多 >
Python中文社区
806 人关注 | 7 讨论 | 144 内容
+ 订阅
查看更多 >
开发与运维
5320 人关注 | 127977 讨论 | 213449 内容
+ 订阅
  • Seata-php 半年规划
  • 【软件开发规范二】《禁止项开发规范》
  • release-it帮我做了哪些自动化的事情
查看更多 >
IoT
122128 人关注 | 2714 讨论 | 18805 内容
+ 订阅
  • 常用的工程测量仪器有哪些
  • 国际机器人联合会:2022年机器人发展的五大趋势
  • 关联数据赋能智能化业务
查看更多 >
大数据
185208 人关注 | 24729 讨论 | 59966 内容
+ 订阅
  • 10亿+/秒!看阿里如何搞定实时数仓高吞吐实时写入与更新
  • 有零有食携手阿里云&瓴羊,开启食品企业智能化转型
  • 常用的工程测量仪器有哪些
查看更多 >