首页 文章 精选 留言 我的

精选列表

搜索[高DPI],共10007篇文章
优秀的个人博客,低调大师

高并发优雅的做限流

技术分析 如果你比较关注现在的技术形式,就会知道微服务现在火的一塌糊涂,当然,事物都有两面性,微服务也不是解决技术,架构等问题的万能钥匙。如果服务化带来的利大于弊,菜菜还是推荐将系统服务化。随着服务化的进程的不断演化,各种概念以及技术随之而来。任何一种方案都是为了解决问题而存在。比如:熔断设计,接口幂等性设计,重试机制设计,还有今天菜菜要说的限流设计,等等这些技术几乎都充斥在每个系统中。 就今天来说的限流,书面意思和作用一致,就是为了限制,通过对并发访问或者请求进行限速或者一个时间窗口内的请求进行限速来保护系统。一旦达到了限制的临界点,可以用拒绝服务、排队、或者等待的方式来保护现有系统,不至于发生雪崩现象。 限流就像做帝都的地铁一般,如果你住在西二旗或者天通苑也许会体会的更深刻一些。我更习惯在技术角度用消费者的角度来阐述,需要限流的一般原因是消费者能力有限,目的为了避免超过消费者能力而出现系统故障。当然也有其他类似的情况也可以用限流来解决。 限流的表现形式上大部分可以分为两大类: 限制消费者数量。也可以说消费的最大能力值。比如:数据库的连接池是侧重的是总的连接数。还有菜菜以前写的线程池,本质上也是限制了消费者的最大消费能力。 可以被消费的请求数量。这里的数量可以是瞬时并发数,也可以是一段时间内的总并发数。菜菜今天要帮YY妹子做的也是这个。 除此之外,限流还有别的表现形式,例如按照网络流量来限流,按照cpu使用率来限流等。按照限流的范围又可以分为分布式限流,应用限流,接口限流等。无论怎么变化,限流都可以用以下图来表示: 常用技术实现 令牌桶算法 令牌桶是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求。令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌。令牌桶中装的是令牌。 漏桶算法 漏桶一个固定容量的漏桶,按照固定常量速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝。漏桶可以看做是一个具有固定容量、固定流出速率的队列,漏桶限制的是请求的流出速率。漏桶中装的是请求。 计数器 有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数,比如数据库连接池、线程池、秒杀的并发数;计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。 除此之外,其实根据不同的业务场景,还可以出现很多不同的限流算法,但是总的规则只有一条:只要符合当前业务场景的限流策略就是最好的 限流的其他基础知识请百度!! 另一种方式解决妹子问题 回归问题,YY妹子的问题,菜菜不准备用以上所说的几种算法来帮助她。菜菜准备用一个按照时间段限制请求总数的方式来限流。 总体思路是这样: 用一个环形来代表通过的请求容器。 用一个指针指向当前请求所到的位置索引,来判断当前请求时间和当前位置上次请求的时间差,依此来判断是否被限制。 如果请求通过,则当前指针向前移动一个位置,不通过则不移动位置 重复以上步骤 直到永远....... 用代码说话才是王道 以下代码不改或者稍微修改可用于生产环境 以下代码的核心思路是这样的:指针当前位置的时间元素和当前时间的差来决定是否允许此次请求,这样通过的请求在时间上表现的比较平滑。 思路远比语言重要,任何语言也可为之,请phper,golanger,javaer 自行实现一遍即可 //限流组件,采用数组做为一个环 class LimitService { //当前指针的位置 int currentIndex = 0; //限制的时间的秒数,即:x秒允许多少请求 int limitTimeSencond = 1; //请求环的容器数组 DateTime?[] requestRing = null; //容器改变或者移动指针时候的锁 object objLock = new object(); public LimitService(int countPerSecond,int _limitTimeSencond) { requestRing = new DateTime?[countPerSecond]; limitTimeSencond= _limitTimeSencond; } //程序是否可以继续 public bool IsContinue() { lock (objLock) { var currentNode = requestRing[currentIndex]; //如果当前节点的值加上设置的秒 超过当前时间,说明超过限制 if (currentNode != null&& currentNode.Value.AddSeconds(limitTimeSencond) >DateTime.Now) { return false; } //当前节点设置为当前时间 requestRing[currentIndex] = DateTime.Now; //指针移动一个位置 MoveNextIndex(ref currentIndex); } return true; } //改变每秒可以通过的请求数 public bool ChangeCountPerSecond(int countPerSecond) { lock (objLock) { requestRing = new DateTime?[countPerSecond]; currentIndex = 0; } return true; } //指针往前移动一个位置 private void MoveNextIndex(ref int currentIndex) { if (currentIndex != requestRing.Length - 1) { currentIndex = currentIndex + 1; } else { currentIndex = 0; } } } 测试程序如下: static LimitService l = new LimitService(1000, 1); static void Main(string[] args) { int threadCount = 50; while (threadCount >= 0) { Thread t = new Thread(s => { Limit(); }); t.Start(); threadCount--; } Console.Read(); } static void Limit() { int i = 0; int okCount = 0; int noCount = 0; Stopwatch w = new Stopwatch(); w.Start(); while (i < 1000000) { var ret = l.IsContinue(); if (ret) { okCount++; } else { noCount++; } i++; } w.Stop(); Console.WriteLine($"共用{w.ElapsedMilliseconds},允许:{okCount}, 拦截:{noCount}"); } 测试结果如下: 最大用时15秒,共处理请求1000000*50=50000000 次 并未发生GC操作,内存使用率非常低,每秒处理 300万次+请求 。以上程序修改为10个线程,大约用时4秒之内 如果是强劲的服务器或者线程数较少情况下处理速度将会更快 写在最后 以上代码虽然简单,但是却为限流的核心代码(其实还有优化余地),经过其他封装可以适用于Webapi的filter或其他场景。妹子问题解决了,要不要让她请我吃个饭呢? 更多精彩文章 分布式大并发系列 架构设计系列 趣学算法和数据结构系列 设计模式系列

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

快速构建高并发微服务

0. 为什么说做好微服务很难? 要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说: 基本功能层面 并发控制&限流,避免服务被突发流量击垮 服务注册与服务发现,确保能够动态侦测增减的节点 负载均衡,需要根据节点承受能力分发流量 超时控制,避免对已超时请求做无用功 熔断设计,快速失败,保障故障节点的恢复能力 高阶功能层面 请求认证,确保每个用户只能访问自己的数据 链路追踪,用于理解整个系统和快速定位特定请求的问题 日志,用于数据收集和问题定位 可观测性,没有度量就没有优化 对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。go-zero微服务框架就是为此而生。 另外,我们始终秉承工具大于约定和文档的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了goctl工具。 下面我通过短链微服务来演示通过go-zero快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单! 1. 什么是短链服务? 短链服务就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串。 写此短链服务是为了从整体上演示go-zero构建完整微服务的过程,算法和实现细节尽可能简化了,所以这不是一个高阶的短链服务。 2. 短链微服务架构图 这里把shorten和expand分开为两个微服务,并不是说一个远程调用就需要拆分为一个微服务,只是为了最简演示多个微服务而已 后面的redis和mysql也是共用的,但是在真正项目里要尽可能每个微服务使用自己的数据库,数据边界要清晰 3. 准备工作 安装etcd, mysql, redis 准备goctl工具 直接从https://github.com/tal-tech/go-zero/releases下载最新版,后续会加上自动更新 也可以从源码编译,在任意目录下进行,目的是为了编译goctl工具 git clone https://github.com/tal-tech/go-zero 在tools/goctl目录下编译goctl工具go build goctl.go 将生成的goctl放到$PATH下,确保goctl命令可运行 创建工作目录shorturl 在shorturl目录下执行go mod init shorturl初始化go.mod 4. 编写API Gateway代码 通过goctl生成shorturl.api并编辑,为了简洁,去除了文件开头的info,代码如下: type ( shortenReq struct { url string `form:"url"` } shortenResp struct { shortUrl string `json:"shortUrl"` } ) type ( expandReq struct { key string `form:"key"` } expandResp struct { url string `json:"url"` } ) service shorturl-api { @server( handler: ShortenHandler ) get /shorten(shortenReq) returns(shortenResp) @server( handler: ExpandHandler ) get /expand(expandReq) returns(expandResp) } type用法和go一致,service用来定义get/post/head/delete等api请求,解释如下: service shorturl-api {这一行定义了service名字 @server部分用来定义server端用到的属性 handler定义了服务端handler名字 get /shorten(shortenReq) returns(shortenResp)定义了get方法的路由、请求参数、返回参数等 使用goctl生成API Gateway代码 goctl api go -api shorturl.api -dir api 生成的文件结构如下: . ├── api │ ├── etc │ │ └── shorturl-api.yaml // 配置文件 │ ├── internal │ │ ├── config │ │ │ └── config.go // 定义配置 │ │ ├── handler │ │ │ ├── expandhandler.go // 实现expandHandler │ │ │ ├── routes.go // 定义路由处理 │ │ │ └── shortenhandler.go // 实现shortenHandler │ │ ├── logic │ │ │ ├── expandlogic.go // 实现ExpandLogic │ │ │ └── shortenlogic.go // 实现ShortenLogic │ │ ├── svc │ │ │ └── servicecontext.go // 定义ServiceContext │ │ └── types │ │ └── types.go // 定义请求、返回结构体 │ └── shorturl.go // main入口定义 ├── go.mod ├── go.sum └── shorturl.api 启动API Gateway服务,默认侦听在8888端口 go run api/shorturl.go -f api/etc/shorturl-api.yaml 测试API Gateway服务 curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn" 返回如下: HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 27 Aug 2020 14:31:39 GMT Content-Length: 15 {"shortUrl":""} 可以看到我们API Gateway其实啥也没干,就返回了个空值,接下来我们会在rpc服务里实现业务逻辑 可以修改internal/svc/servicecontext.go来传递服务依赖(如果需要) 实现逻辑可以修改internal/logic下的对应文件 可以通过goctl生成各种客户端语言的api调用代码 到这里,你已经可以通过goctl生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档 5. 编写shorten rpc服务 在rpc/shorten目录下编写shorten.proto文件 可以通过命令生成proto文件模板 goctl rpc template -o shorten.proto 修改后文件内容如下: syntax = "proto3"; package shorten; message shortenReq { string url = 1; } message shortenResp { string key = 1; } service shortener { rpc shorten(shortenReq) returns(shortenResp); } 用goctl生成rpc代码,在rpc/shorten目录下执行命令 goctl rpc proto -src shorten.proto 文件结构如下: rpc/shorten ├── etc │ └── shorten.yaml // 配置文件 ├── internal │ ├── config │ │ └── config.go // 配置定义 │ ├── logic │ │ └── shortenlogic.go // rpc业务逻辑在这里实现 │ ├── server │ │ └── shortenerserver.go // 调用入口, 不需要修改 │ └── svc │ └── servicecontext.go // 定义ServiceContext,传递依赖 ├── pb │ └── shorten.pb.go ├── shorten.go // rpc服务main函数 ├── shorten.proto └── shortener ├── shortener.go // 提供了外部调用方法,无需修改 ├── shortener_mock.go // mock方法,测试用 └── types.go // request/response结构体定义 直接可以运行,如下: $ go run shorten.go -f etc/shorten.yaml Starting rpc server at 127.0.0.1:8080... etc/shorten.yaml文件里可以修改侦听端口等配置 6. 编写expand rpc服务 在rpc/expand目录下编写expand.proto文件 可以通过命令生成proto文件模板 goctl rpc template -o expand.proto 修改后文件内容如下: syntax = "proto3"; package expand; message expandReq { string key = 1; } message expandResp { string url = 1; } service expander { rpc expand(expandReq) returns(expandResp); } 用goctl生成rpc代码,在rpc/expand目录下执行命令 goctl rpc proto -src expand.proto 文件结构如下: rpc/expand ├── etc │ └── expand.yaml // 配置文件 ├── expand.go // rpc服务main函数 ├── expand.proto ├── expander │ ├── expander.go // 提供了外部调用方法,无需修改 │ ├── expander_mock.go // mock方法,测试用 │ └── types.go // request/response结构体定义 ├── internal │ ├── config │ │ └── config.go // 配置定义 │ ├── logic │ │ └── expandlogic.go // rpc业务逻辑在这里实现 │ ├── server │ │ └── expanderserver.go // 调用入口, 不需要修改 │ └── svc │ └── servicecontext.go // 定义ServiceContext,传递依赖 └── pb └── expand.pb.go 修改etc/expand.yaml里面的ListenOn的端口为8081,因为8080已经被shorten服务占用了 修改后运行,如下: $ go run expand.go -f etc/expand.yaml Starting rpc server at 127.0.0.1:8081... etc/expand.yaml文件里可以修改侦听端口等配置 7. 修改API Gateway代码调用shorten/expand rpc服务 修改配置文件shorter-api.yaml,增加如下内容 Shortener: Etcd: Hosts: - localhost:2379 Key: shorten.rpc Expander: Etcd: Hosts: - localhost:2379 Key: expand.rpc 通过etcd自动去发现可用的shorten/expand服务 修改internal/config/config.go如下,增加shorten/expand服务依赖 type Config struct { rest.RestConf Shortener rpcx.RpcClientConf // 手动代码 Expander rpcx.RpcClientConf // 手动代码 } 修改internal/svc/servicecontext.go,如下: type ServiceContext struct { Config config.Config Shortener rpcx.Client // 手动代码 Expander rpcx.Client // 手动代码 } func NewServiceContext(config config.Config) *ServiceContext { return &ServiceContext{ Config: config, Shortener: rpcx.MustNewClient(config.Shortener), // 手动代码 Expander: rpcx.MustNewClient(config.Expander), // 手动代码 } } 通过ServiceContext在不同业务逻辑之间传递依赖 修改internal/logic/expandlogic.go,如下: type ExpandLogic struct { ctx context.Context logx.Logger expander rpcx.Client // 手动代码 } func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic { return ExpandLogic{ ctx: ctx, Logger: logx.WithContext(ctx), expander: svcCtx.Expander, // 手动代码 } } func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) { // 手动代码开始 resp, err := expander.NewExpander(l.expander).Expand(l.ctx, &expander.ExpandReq{ Key: req.Key, }) if err != nil { return nil, err } return &types.ExpandResp{ Url: resp.Url, }, nil // 手动代码结束 } 增加了对expander服务的依赖,并通过调用expander的Expand方法实现短链恢复到url 修改internal/logic/shortenlogic.go,如下: type ShortenLogic struct { ctx context.Context logx.Logger shortener rpcx.Client // 手动代码 } func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLogic { return ShortenLogic{ ctx: ctx, Logger: logx.WithContext(ctx), shortener: svcCtx.Shortener, // 手动代码 } } func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) { // 手动代码开始 resp, err := shortener.NewShortener(l.shortener).Shorten(l.ctx, &shortener.ShortenReq{ Url: req.Url, }) if err != nil { return nil, err } return &types.ShortenResp{ ShortUrl: resp.Key, }, nil // 手动代码结束 } 增加了对shortener服务的依赖,并通过调用shortener的Shorten方法实现url到短链的变换 至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache 8. 定义数据库表结构,并生成CRUD+cache代码 shorturl下创建rpc/model目录:mkdir -p rpc/model 在rpc/model目录下编写创建shorturl表的sql文件shorturl.sql,如下: CREATE TABLE `shorturl` ( `shorten` varchar(255) NOT NULL COMMENT 'shorten key', `url` varchar(255) NOT NULL COMMENT 'original url', PRIMARY KEY(`shorten`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 创建DB和table create database gozero; source shorturl.sql; 在rpc/model目录下执行如下命令生成CRUD+cache代码,-c表示使用redis cache goctl model mysql ddl -c -src shorturl.sql -dir . 也可以用datasource命令代替ddl来指定数据库链接直接从schema生成 生成后的文件结构如下: rpc/model ├── shorturl.sql ├── shorturlmodel.go // CRUD+cache代码 └── vars.go // 定义常量和变量 9. 修改shorten/expand rpc代码调用crud+cache代码 修改rpc/expand/etc/expand.yaml,增加如下内容: DataSource: root:@tcp(localhost:3306)/gozero Table: shorturl Cache: - Host: localhost:6379 可以使用多个redis作为cache,支持redis单点或者redis集群 修改rpc/expand/internal/config.go,如下: type Config struct { rpcx.RpcServerConf DataSource string // 手动代码 Table string // 手动代码 Cache cache.CacheConf // 手动代码 } 增加了mysql和redis cache配置 修改rpc/expand/internal/svc/servicecontext.go,如下: type ServiceContext struct { c config.Config Model *model.ShorturlModel // 手动代码 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ c: c, Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码 } } 修改rpc/expand/internal/logic/expandlogic.go,如下: type ExpandLogic struct { ctx context.Context logx.Logger model *model.ShorturlModel // 手动代码 } func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ExpandLogic { return &ExpandLogic{ ctx: ctx, Logger: logx.WithContext(ctx), model: svcCtx.Model, // 手动代码 } } func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) { // 手动代码开始 res, err := l.model.FindOne(in.Key) if err != nil { return nil, err } return &expand.ExpandResp{ Url: res.Url, }, nil // 手动代码结束 } 修改rpc/shorten/etc/shorten.yaml,增加如下内容: DataSource: root:@tcp(localhost:3306)/gozero Table: shorturl Cache: - Host: localhost:6379 可以使用多个redis作为cache,支持redis单点或者redis集群 修改rpc/shorten/internal/config.go,如下: type Config struct { rpcx.RpcServerConf DataSource string // 手动代码 Table string // 手动代码 Cache cache.CacheConf // 手动代码 } 增加了mysql和redis cache配置 修改rpc/shorten/internal/svc/servicecontext.go,如下: type ServiceContext struct { c config.Config Model *model.ShorturlModel // 手动代码 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ c: c, Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码 } } 修改rpc/shorten/internal/logic/shortenlogic.go,如下: const keyLen = 6 type ShortenLogic struct { ctx context.Context logx.Logger model *model.ShorturlModel // 手动代码 } func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortenLogic { return &ShortenLogic{ ctx: ctx, Logger: logx.WithContext(ctx), model: svcCtx.Model, // 手动代码 } } func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) { // 手动代码开始,生成短链接 key := hash.Md5Hex([]byte(in.Url))[:keyLen] _, err := l.model.Insert(model.Shorturl{ Shorten: key, Url: in.Url, }) if err != nil { return nil, err } return &shorten.ShortenResp{ Key: key, }, nil // 手动代码结束 } 至此代码修改完成,凡事手动修改的代码我加了标注 10. 完整调用演示 shorten api调用 curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn" 返回如下: HTTP/1.1 200 OK Content-Type: application/json Date: Sat, 29 Aug 2020 10:49:49 GMT Content-Length: 21 {"shortUrl":"f35b2a"} expand api调用 curl -i "http://localhost:8888/expand?key=f35b2a" 返回如下: HTTP/1.1 200 OK Content-Type: application/json Date: Sat, 29 Aug 2020 10:51:53 GMT Content-Length: 34 {"url":"http://www.xiaoheiban.cn"} 11. Benchmark 因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了expand接口,相当于从mysql里读取并利用缓存,shorten.lua里随机从db里获取了100个热key来生成压测请求 可以看出在我的MacBook Pro上能达到3万+的qps。 12. 总结 我们一直强调工具大于约定和文档。 go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。 我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。 通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。 13. 微信交流群

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

高德地图之轨迹回放

最近项目中有需要做车辆的轨迹回放功能,在这里就给打分享一下还是挺简单的。 <!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"> <title>轨迹回放</title> <link href="styles/NewGlobal.css" rel="stylesheet" /> <link rel="stylesheet" href="css/gaode_main1119.css" /> <link rel="stylesheet" type="text/css" href="css/ui.css"> <!-- <link type="text/css" rel="stylesheet" href="css/sale-list.css" /> --> <link rel="stylesheet" href="css/demo-center.css"/> <script type="text/javascript" src="jquery/1.9.1/jquery.min.js"></script> <style> html, body, #container { height: 98%; width: 100%; } .input-card .btn{ margin-right: 1.2rem; width: 9rem; } .input-card .btn:last-child{ margin-right: 0; } </style> </head> <body> <div id="container"></div> <div class="input-card" style="margin-bottom:45px"> <div style="" align="center"><span id="tsid" style="font-size: 14px"></span></div> <h4>轨迹回放控制</h4> <div class="input-item"> <input type="button" class="btn" style="width: 25%" value="今天" id="start1" onclick="gettrack('1')"/> <input type="button" class="btn" style="width: 25%" value="昨天" id="start2" onclick="gettrack('2')"/> <input type="button" class="btn" style="width: 25%" value="前天" id="start3" onclick="gettrack('3')"/> <input type="button" class="btn" value="开始回放" id="start" onclick="startAnimation()"/> <input type="button" class="btn" value="暂停回放" id="pause" onclick="pauseAnimation()"/> </div> </div> <div class="aui-l-content" style="width:100%;margin: 0 auto; bottom: 0px; position: fixed;"> <div class="aui-menu-list aui-menu-list-clear"> <ul> <li class="b-line"> <a href="new_index.html"> <div class="aui-icon"><img src="images/icon-home/my-in2.png"></div> <h3>返回主页</h3> <div class="aui-time"><i class="aui-jump"></i></div> </a> </li> </ul> </div> </div> <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.10&key="></script> <script> var marker, lineArr=new Array(),tishiArr=new Array(); $(function(){ var h = document.documentElement.clientHeight || document.body.clientHeight; $("#container").css("height", h-52-77-12-50); tomap(); }) //lineArr数组为历史回顾的GPS点 function tomap(){ var map = new AMap.Map("container", { resizeEnable: true, center: [104.04, 30.40], zoom: 14 }); map.clearMap(); // 清除地图覆盖物 marker = new AMap.Marker({ map: map, position: lineArr[0], icon: "images/car.png", offset: new AMap.Pixel(-26, -13), autoRotation: true, angle:-90, }); // 绘制轨迹 var polyline = new AMap.Polyline({ map: map, path: lineArr, showDir:true, strokeColor: "#28F", //线颜色 // strokeOpacity: 1, //线透明度 strokeWeight: 6, //线宽 // strokeStyle: "solid" //线样式 }); var passedPolyline = new AMap.Polyline({ map: map, // path: lineArr, strokeColor: "#AF5", //线颜色 // strokeOpacity: 1, //线透明度 strokeWeight: 6, //线宽 // strokeStyle: "solid" //线样式 }); marker.on('moving', function (e) { passedPolyline.setPath(e.passedPath); }); map.setFitView(); } var i=0; var t1; var k=0; function addinfo(){ $("#tsid").html(tishiArr[i]); i=i+1; if(i>tishiArr.length){ clearInterval(t1); i=0; } } //开始 function startAnimation () { if(lineArr.length>0){ if(k>0){ marker.resumeMove(); }else{ marker.moveAlong(lineArr, 350); } t1=window.setInterval(addinfo, 500); } } //继续 function pauseAnimation () { k=1; clearInterval(t1); if(lineArr.length>0){ marker.pauseMove(); } } // function resumeAnimation () { marker.resumeMove(); } //停止 function stopAnimation () { if(lineArr.length>0){ marker.stopMove(); } } </script> </body> </html> 上个图吧这里: ​​​ 是不是就是很简单的操作。 如有需要可以加我Q群【308742428】大家一起讨论技术。 后面会不定时为大家更新文章,敬请期待。 喜欢的朋友可以关注下。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

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

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册