Sporza如何创建实时体育数据API?经验分享

As part of our services we deliver a football app “Sporza Voetbal”, available for iOS and Android. With this app you can follow football matches live. Whilst matches are played, you are kept up to date of all interesting events and evolutions. All of this for a variety of competitions, with their corresponding rankings and calendars. In order to make this possible a specific technical approach is needed to deliver all the data correct and fast to the app.

作为我们服务的一部分,我们提供了足球应用程序“ Sporza Voetbal”,可用于iOS和Android。 有了这个程序,您可以实时观看足球比赛。 进行比赛时,您会随时了解所有有趣的事件和演变。 所有这些都适合各种比赛,以及相应的排名和日历。 为了使之成为可能,需要一种特定的技术方法来将所有数据正确,快速地交付给应用程序。

In this article we give you an overview of what we experienced and achieved while creating an additional backend for this new IOS and Android Soccer app. We hope to share our lessons learned and would appreciate any feedback.

在本文中,我们为您概述了我们在为这个新的IOS和Android Soccer应用创建额外的后端时所经历和取得的成就。 我们希望分享我们的经验教训,并希望收到任何反馈。

第1章:我们需要什么? (Chapter 1: What do we need?)

We need a backend for a new soccer app, what to do?Thefirst option was to expand our existing microservices so they would be able to handle the new endpoints needed for the app.Doing this would result in changes to legacy code and would leave us with a less versatile way of deploying.

我们需要一个新世界杯应用程序的后端,该怎么做?第一个选择是扩展我们现有的微服务,以便它们能够处理该应用程序所需的新端点,这样做会导致遗留代码的更改并留下我们以一种不太通用的部署方式。

The second option was to create an extra layer that would interact with the app and our existing sport backend.

第二个选择是创建一个额外的层,该层将与应用程序和我们现有的运动后端交互。

Since we will also refactor our database layer later this year, we decided to go with the second option. We created an extra layer and called it the “Backend For Frontend” or “BFF”. (Backend For Frontend).

由于我们还将在今年晚些时候重构数据库层,因此我们决定选择第二种方法。 我们创建了一个额外的层,并将其称为“后端后端”或“ BFF”。 ( 后端为前端 )。

So we decided to create a new layer for the app to communicate with.

因此,我们决定为应用程序创建一个与之通信的新层。

Let’s talk requirements.Real-time sport event updates:An improvement we always wanted in our previous app was faster updates on sport events. This was hard to do due to the existing caching architecture we were using. Because our new BFF would have its own caching layer, this should give us a chance to take a look at this again.

让我们谈谈需求。 实时体育赛事更新:我们在以前的应用中一直希望获得的一项改进是更快地更新体育赛事。 由于我们正在使用现有的缓存体系结构,因此很难做到这一点。 因为我们的新BFF将具有自己的缓存层,所以这应该使我们有机会再次进行研究。

Cost effective:Another thing we were looking to solve in our current backend was the running cost of the servers. Scaling out and paying when large sport events go live is not the issue here, but having a lot of running microservices that are barely handling any requests for a long time is. All of our microservices are running in a redundant mode but some of these have no load to process when there are no sport activities.

具有成本效益:我们希望在当前后端中解决的另一件事是服务器的运行成本。 在大型体育赛事上线时向外扩展并付费不是这里的问题,但拥有大量正在运行的微服务却长时间无法处理任何请求却是问题。 我们所有的微服务都以冗余模式运行,但是其中的一些微服务在没有体育活动时没有负载可处理。

Scalable for large sport events:We are already doing this, but this is extremely important so we cannot lose track of it. During large sport events we are handling thousands of requests per minute. Caching, scaling and other optimizations are very important. For example: during the world championship of 2018 we had a day with 850.000 unique visitors.

可扩展用于大型体育赛事:我们已经在这样做,但是这是非常重要的,因此我们不会丢失它。 在大型体育赛事中,我们每分钟处理数千个请求。 缓存,缩放和其他优化非常重要。 例如:在2018年世界锦标赛期间,我们有85万名独立访客 。

Continuous delivery with short deploy times:We deploy a lot. A week with multiple deploys is not uncommon. Deploying with our current microservice backend can take up to a few hours: spinning up new servers, deploying code, health checks, … To be fair, doing this in the cloud has already improved the process as opposed to before where we needed weeks or even months on our on-premise hardware. But what if we could do this in minutes instead of hours?

连续交付,部署时间短:我们部署了很多东西。 有多个部署的一周并不罕见。 使用我们当前的微服务后端进行部署可能要花费几个小时:旋转新服务器,部署代码,运行状况检查…………公平地说,在云中进行此操作已经改善了流程,而之前需要几周甚至是几周的时间在我们的本地硬件上使用了几个月。 但是,如果我们可以在数分钟而不是数小时内做到这一点呢?

Fast response times:In order to have a responsive app, the BFF needs to handle the requests as fast as possible, even for personalized api calls.

快速的响应时间:为了拥有响应式应用,BFF需要尽快处理请求,即使是个性化的api调用也是如此。

第2章:POC吧! (Chapter 2: POC it!)

After we decided on the specific requirements, it was time to build our first proof of concept. We decided to go serverless and use AWS Lambda to solve some of our needs. The pay-per-request model of Lambda makes it very cost-effective because you only pay for what you use. Furthermore, scaling is fully automated and even deploying is very easy and fast. This combination makes it a very promising platform.

在确定了特定要求之后,该构建我们的第一个概念证明了。 我们决定不使用服务器,并使用AWS Lambda解决我们的一些需求。 Lambda的按请求付费模式使其非常具有成本效益,因为您只为使用的商品付费。 此外,扩展是完全自动化的,甚至部署也非常容易和快速。 这种结合使其成为一个非常有前途的平台。

So, what about real-time events and fast response times? Unfortunately, not all of our problems were magically solved just by using lambdas.

那么,实时事件和快速响应时间又如何呢? 不幸的是,并非仅仅使用lambda就能神奇地解决我们所有的问题。

We experimented by creating a few lambda endpoints in NodeJs and Java11. Spring boot serverless was used for the Java11 lambda. For NodeJs we used serverless-http and typescript.

我们通过在NodeJ和Java11中创建一些lambda端点进行了实验。 Spring引导无服务器用于Java11 lambda。 对于NodeJ,我们使用了serverless -http和typescript。

Let’s list some pros and cons we experienced.

让我们列出一些我们经历过的利弊。

NodeJs with typescript:Pros: — Small package size — Fast cold start — Small memory footprint — Cheaper to run

带有打字稿的NodeJ:优点:—小型包装—快速冷启动—内存占用量小—运行成本低

Cons: — We only had java developers available… — Testability (so many frameworks to choose from)

缺点:—我们只有Java开发人员可用…—可测试性(可供选择的框架很多)

Java11:Pros: — We had plenty of Java developers available — Fast when warm — Testability (Junit is the clear winner)

Java11:优点:-我们有大量的Java开发人员可用-温暖时快速-可测试性(Junit无疑是赢家)

Cons: — Very slow cold start — Large memory footprint — More expensive to run

缺点:—启动很慢—内存占用很大—运行成本更高

What does cold start mean? A lambda function is not always running. It’s just an executable file that is waiting to be started and executed. An initial start is done when someone actually visits the endpoint contained in that executable file. That person needs to wait until the software is booted and ready to respond.

冷启动是什么意思? Lambda函数并非始终运行。 它只是一个等待启动和执行的可执行文件。 当某人实际访问该可执行文件中包含的端点时,即完成了初始启动。 该人员需要等待,直到软件启动并准备响应为止。

For Java, this means that the JVM needs to start, load all classes and libraries, load all statics, wire all beans and only then it can start processing the request.AWS Lambda will now keep this program loaded after the initial request and, for the following X minutes, all requests will hit a “warm” lambda, resulting in a much faster response.

对于Java,这意味着JVM需要启动,加载所有类和库,加载所有静态变量,连接所有Bean,然后才可以开始处理请求.AWS Lambda现在将在初始请求之后保持该程序的加载,并且在接下来的X分钟内,所有请求都会达到“暖通” lambda,从而使响应速度更快。

Let’s visualize the cold startup.

让我们可视化冷启动。

Considering all of this, why did we choose to use Java11? The fact that we only had java developers available played a big part in that decision. Development cost would be lower and compensate for the higher running cost for Java. Of course we still had to deal with the very slow cold starts for Java lambda.

考虑到所有这些,我们为什么选择使用Java11? 我们只有Java开发人员可用这一事实在该决定中起了很大的作用。 开发成本将更低,并且可以弥补Java更高的运行成本。 当然,对于Java lambda,我们仍然必须处理非常缓慢的冷启动。

The first experiment was to switch from Spring to Micronaut. Micronaut has shorter startup times and a smaller memory footprint. GraalVm can also be used with Micronaut, but more on that later.

第一个实验是从Spring切换到Micronaut 。 Micronaut具有较短的启动时间和较小的内存占用。 GraalVm也可以与Micronaut一起使用,但以后会更多。

This resulted in a reduction of the cold start by 4 seconds.

这导致冷启动减少了4秒。

A reduction of 4 seconds is already a lot, but a cold startup time of 6 seconds was still not acceptable. Luckily, AWS was working on a solution for this called provisioned concurrency. This feature negates my previous statement of “Pay per request”. AWS will start X amount of instances of your Lambda function immediately and keeps them warm. This means that there are a minimum amount of lambdas running and no one will have to wait when there is not too much load. It is still possible that AWS would scale up the lambdas on heavy load, but we are counting on our CDN to handle this so we will not have to scale up frequently.

减少4秒已经是很多了,但是6秒的冷启动时间仍然不可接受。 幸运的是,AWS正在开发一种称为预配置并发的解决方案。 此功能使我之前的“按请求付款”声明无效。 AWS将立即启动X数量的Lambda函数实例,并使它们保持温暖。 这意味着运行的lambda数量最少,并且在负载不太多时无需等待。 AWS仍然有可能在繁重的负载下扩展lambda,但是我们依靠CDN来处理此问题,因此我们不必经常扩展。

What about GraalVm? GraalVm compiles your Java application to a native executable file. A lot of runtime optimisations are therefore done at compile time. This results in long compile times but very fast startup times and smaller memory footprints. However, while doing a POC we noticed that this added unacceptable complexity to our project.

那GraalVm呢? GraalVm将Java应用程序编译为本地可执行文件。 因此,在编译时会进行很多运行时优化。 这样会导致编译时间长,但启动时间却非常快,并且占用的内存更少。 但是,在进行POC时,我们注意到这给我们的项目增加了不可接受的复杂性。

第三章:开发! (Chapter 3: Develop it!)

Using the Micronaut CLI makes it fairly simple to set up your project. We did experience some bugs with an earlier version like maven pom files that were not configured correctly after generating the project. However, an updated version a few week later solved most of our issues. Keep in mind that Micronaut is still a very active project and another major release is on it’s way.

使用Micronaut CLI可以很容易地设置项目。 比如我们开发NBA直播吧资源我们确实遇到过一些早期版本的错误,例如生成项目后未正确配置的maven pom文件。 但是,几周后的更新版本解决了我们的大多数问题。 请记住,Micronaut仍然是一个非常活跃的项目,并且即将发布另一个主要版本。

Let’s take a closer look at the simplified version of our BFF.

让我们仔细看一下BFF的简化版本。

Here you can see 3 lambdas. Each lambda serves a specific purpose and can contain multiple endpoints. Our initial version of a feature-complete BFF contains over 13 lambdas with a total of 35 endpoints.

在这里您可以看到3个lambda。 每个lambda都有特定的用途,可以包含多个端点。 我们的功能完善的BFF的初始版本包含13个以上的lambda,共有35个端点。

Everything started off really smooth and all developers were happy because of the fast development cycle we could achieve by splitting the lambdas like this. So the first reality check should not be too far off then? Exactly!

一切开始时都非常顺利,并且所有开发人员都很高兴,因为我们可以通过分割lambda来实现快速的开发周期。 那么第一次现实检查应该不会太遥远吗? 究竟!

挑战1:冷启动 (Challenge 1: Cold starts)

Remember the provisioned concurrency that would solve all our cold start problems? Well, not so much… Or at least not just like that. Enabling this feature would improve the startup time, but not in a stable reproducible way. Cold start requests to the lambda would vary in response times between 1 and 6 seconds. It took a while to research this, but eventually we discovered it was the way Micronaut initialized its beans that would cause the provisioned concurrency feature not to behave as expected. Micronaut lazily initializes all the beans by default. That means provisioned concurrency can start your jvm, load classes and libraries, load all statics and then stop there. We are missing the bean wiring here since Micronaut would do this on receiving the first request.

还记得可以解决我们所有冷启动问题的预配置并发吗? 好吧,不是那么多……或者至少不是那样。 启用此功能将缩短启动时间,但不会以稳定的可复制方式进行。 对lambda的冷启动请求的响应时间将在1到6秒之间变化。 经过一段时间的研究,但最终我们发现,这是Micronaut初始化其bean的方式,这会导致预配置的并发功能无法按预期运行。 默认情况下,Micronaut惰性地初始化所有bean。 这意味着预配置的并发性可以启动您的jvm,加载类和库,加载所有静态变量,然后在此处停止。 由于Micronaut会在收到第一个请求时执行此操作,因此我们在这里缺少Bean接线。

Solution:In version 1.3.X of Micronaut you can annotate all your beans with @Context to wire them on start up. From version 2.X.X it is possible to use the eagerInitSingletons and eagerInitConfiguration methods from the ApplicationContextBuilder to configure this.

解决方案:在Micronaut的1.3.X版中,您可以使用@Context注释所有bean,以在启动时连接它们。 从版本2.XX开始 ,可以使用ApplicationContextBuilder中的eagerInitSingletons和eagerInitConfiguration方法进行配置。

At the start of Chapter 2 we had some requirements to solve: fast response times and real-time events. Let’s consider fast response times as fixed since we solved all the cold start issues and use a content delivery network for caching.

在第2章开始时,我们需要解决一些要求:快速响应时间和实时事件。 让我们考虑快速响应时间是固定的,因为我们解决了所有冷启动问题并使用内容交付网络进行缓存。

挑战2:实时事件 (Challenge 2: Real-time events)

Our existing applications already deliver real-time events, or rather near-real-time events. They are fast, but we are limited by the fact that AWS cloudfront works with a time to live (TTL) mechanism for caching. If an endpoint for match events would have a TTL of 15 seconds, then you would not be able to get updates faster than every 15 seconds. To make it even harder, Cloudfront is not our only caching layer. We are using multiple caching mechanisms that stack up these TTL timings. Simply turning down all TTL’s is not a valid option since our other backend services and databases would not be able to handle that kind of load.

我们现有的应用程序已经交付了实时事件,或者说是近实时事件。 它们的速度很快,但是我们受到AWS Cloudfront使用生存时间(TTL)缓存机制的事实的限制。 如果匹配事件的端点的TTL为15秒,那么您将无法获得比每15秒更快的更新。 更难的是,Cloudfront并不是我们唯一的缓存层。 我们正在使用多种缓存机制来叠加这些TTL时序。 仅关闭所有TTL并不是一个有效的选择,因为我们的其他后端服务和数据库将无法处理这种负载。

Solution:We created a dynamic TTL calculator that would calculate if a match is near its live status. The closer a match is to being live, the lower the TTL will be. Matches in the far future or past can be cached for a long time since nothing is changing. Matches that are live, however, could get new events every few seconds. A TTL timeline for one specific match would look like this.

解决方案:我们创建了一个动态TTL计算器,可以计算匹配是否接近实时状态。 距离比赛越近,TTL越低。 由于未发生任何变化,因此可以将很长一段时间内的比赛保留在很长的将来。 但是,进行中的比赛可能每隔几秒钟就会收到新的事件。 一场特定比赛的TTL时间线如下所示。

挑战3:lambda之间的代码共享 (Challenge 3: Code sharing between lambdas)

Some code needs to be shared between lambdas, models, services, repositories, …We chose to do this at compile time by creating common modules that are included with Maven. This means that whenever there is a change to one of the common modules all the lambdas need to be recompiled and deployed. Doing it like this is good enough for now, but it could be better.

需要在lambda,模型,服务,存储库之间共享一些代码,…我们选择在编译时通过创建Maven随附的通用模块来做到这一点。 这意味着,只要对公共模块之一进行了更改,就需要重新编译和部署所有的lambda。 这样做现在已经足够了,但是可能会更好。

Solution:Lambda Layers would probably be a better solution, but we discovered this too close to our deadline to refactor it. This will probably be an improvement in the future.

解决方案: Lambda Layers可能是一个更好的解决方案,但是我们发现这个离我们的重构期限太近了。 将来可能会有所改善。

挑战4:不要超载数据库 (Challenge 4: Don’t overload the database)

We already have caching in front of our lambdas but our database needs a bit more protection against heavy load. Also in our previous challenge we mentioned that we are reusing repositories by recompiling them in the lambdas. Something else to keep in mind is that AWS can scale the lambdas, we could get in trouble if all these instances would interact with our backend database directly. We also needed something to share the cache between our lambda instances.

我们已经在lambda前面进行了缓存,但是我们的数据库需要更多的保护以防重载。 同样,在我们之前的挑战中,我们提到我们通过在lambda中重新编译存储库来重用它们。 还有一点要记住的是,AWS可以扩展lambda,如果所有这些实例都将直接与我们的后端数据库进行交互,我们可能会遇到麻烦。 我们还需要一些东西来在我们的lambda实例之间共享缓存。

Solution:We added a caching layer between our lambda’s and the backend service by using Micronaut cache with Redis support. This way we can add caching on methods by adding @Cacheable to the methods we want. Using a redis cache to share data between lambda instances is recommended by AWS and is faster than caching http requests. This way makes it possible to cache small parts of a bigger picture, it is even possible to cache small fragments of personalized calls.

解决方案:通过使用具有Redis支持的Micronaut缓存,我们在lambda和后端服务之间添加了一个缓存层。 这样,我们可以通过将@Cacheable添加到所需的方法来在方法上添加缓存。 AWS建议使用redis缓存在lambda实例之间共享数据,它比缓存http请求更快。 这种方式可以缓存较大图片的一小部分,甚至可以缓存个性化呼叫的小片段。

荣誉奖 (Honorable mentions)

将SSL与HttpClient一起使用很重: (Using SSL with HttpClient is heavy:)

Using the HttpClient to call https urls makes Java load ssl libraries at startup.We noticed longer startups and higher memory consumption when calling https urls with the HttpClient. Since our lambdas communicate exclusively to our own, well known services, we were able to use http exclusively and bypass the overhead of loaded SSL libraries.

使用HttpClient调用https URL可以使Java在启动时加载ssl库。我们注意到,使用HttpClient调用https url时,启动时间更长,内存消耗更高。 由于我们的Lambda仅与我们自己的知名服务进行通信,因此我们能够专门使用http并绕过已加载的SSL库的开销。

Lambda启动阶段: (Lambda startup phases:)

You need to configure the amount of ram your lambda can use on AWS. The CPU performance also scales when requesting more or less ram. It is good to know that AWS will allow a higher burst on the CPU during the startup phase. This includes starting the JVM, loading classes and loading statics. From that point on you will fall back to the normal CPU performance. It is recommended to do as much logic as possible during the startup phase.

您需要配置您的lambda在AWS上可以使用的ram数量。 当请求更多或更少的内存时,CPU性能也会提高。 很高兴知道,AWS将在启动阶段允许更高的CPU突发次数。 这包括启动JVM,加载类和加载静态变量。 从那时起,您将恢复到正常的CPU性能。 建议在启动阶段执行尽可能多的逻辑。

端点版本控制: (Endpoint versioning:)

Endpoints that are used for apps are usually versioned to be able to support multiple app versions at the same time. Lambdas can easily be versioned as well, you only need some magic on the api gateway to point your versioned endpoints to the correct versioned lambda.

通常,对用于应用程序的端点进行版本控制,以使其能够同时支持多个应用程序版本。 Lambda也可以轻松地进行版本控制,您只需要在api网关上使用一些魔术,即可将版本控制的端点指向正确的版本化的Lambda。

结论 (Conclusion)

We are still learning a lot about running serverless every day, but we believe that we created a solid foundation that is easily maintainable and upgradable. Some fine-tuning will surely be needed after launching and a bit more deep diving in the advanced features (such as layered lambdas) would not be bad either. Did we make all the right choices? Maybe. It was a struggle to get java to perform like we wanted in this setup and it is hard not to wonder what would have happened if we went with NodeJs. Our initial tests, however, look very promising, but you can take a look yourself.

我们仍然每天都在学习关于无服务器运行的很多知识,但是我们相信我们创建了一个易于维护和升级的坚实基础。 在启动后肯定需要进行一些微调,对高级功能(例如分层的lambda)进行更深入的研究也不是一件坏事。 我们做出了所有正确的选择吗? 也许。 要使Java像我们在此设置中所希望的那样执行是一项艰巨的工作,很难不怀疑如果使用NodeJ会发生什么。 但是,我们的初步测试看起来很有希望,但是您可以自己看看。

Available for Android and IOS

适用于Android和IOS
  参考文档:http://www.chdlav.com/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值