LOL技术文档之客户端重构

THE ARCHITECTURE OF THE LEAGUE CLIENT UPDATE

Greetings! My name is Andrew McVeigh, and I'm a software architect at Riot.

We're in the latter stages of re-engineering the League of Legends client. We're calling this the League client update. In this post, I’ll outline the software architecture of this update and provide some background to the choices we made by pointing out some of the limitations of the current (original) client. The journey to our final architecture has been technically fascinating, and I’m excited to be able to share it with you!

本文将介绍这次客户端重构的背景,以及面临的问题。

WHY REPLACE THE CLIENT?

We built our client in 2008 on a front-end technology called Adobe AIR, which uses an RTMP session-based networking protocol to talk to our servers. This platform served us well: it offered a rich multi-media runtime with animation and effects that just weren’t possible with HTML at the time. Additionally, it was cross platform and made building screens easy for a team that was light on artists and designers.

Fast forward seven years to 2015 and three particular issues with the AIR client have become very stark. These are the issues that our new architecture solves for, amongst other things.

Issue #1 - HTML5 is a standardized, widely adopted platform

Pure HTML5 and JavaScript have become viable desktop client technologies. This brings many advantages including standardized workflows and development tools, along with a large pool of talented developers. We really want to fully harness this platform and the media integrations it allows.

做客户端的时候还是在2008年,那个时候用的前端技术是Adobe Air,这东西使用了RTMP协议来和服务器通信。为什么要用这个呢?因为它夸平台,用起来比较方便。

到今天有三大问题出来了

1.HTML5 是一个标准,广泛适配的平台了。

纯HTML5和JS已经是桌面应用程序的王者了。我们想利用它这个优势。

Issue #2 - Players want to stay connected in and out of game

You wouldn’t want to leave the AIR client running constantly due to its high resource usage even when it is in the background, yet expectations around online connectedness have radically changed in the last few years. Players want to be connected to their friends in and out of game—no one should miss a game invite just because they aren’t currently in front of their PC with the client open (something we're also working to solve with our mobile app).

Air占用的资源比较多,即使它运行在后台也一样。玩家期望能一直连接,而不会经常断线。

 

Issue #3 - Riot dev teams want to work in harmony

League (and also Riot) has grown hugely since 2008 and we could never have guessed that we’d end up with so many teams wanting to add so many features to the client. The original codebase was structured around a small, highly cohesive team and didn’t give the required level of autonomy and independence as the number of features teams grew.

This problem of dev teams colliding with each other wasn’t going away—it was getting worse over time as we added new features. In addition, we aspire to be a multi-game studio, which brings many other challenges and opportunities.

 

INCREMENTING TOWARDS AN AWESOME ARCHITECTURE

There are a number of ways to solve for the above issues—for instance, we spent some serious time reorganizing the existing AIR code with the intention of staying on the (outdated) AIR browser. However, while exploring this option in depth, we realized that we could solve the above issues in a more powerful and sustainable way by changing our platform. We decided to do this despite being completely aware of the dangers of rewrites, because we believe the benefits to the player will outweigh the risks in the long-run.

As such, we chose to start again and build around Chromium Embedded Framework (CEF) as our front-end. This gives us a super strong HTML5 browser component that we can easily customize.

Let me now explain how we found our way to our final architecture.

其实有很多方式来解决这个问题,例如,可以优化现有的Air代码,尽管它已经落后了。但是深度研究后发现,其实换个平台可能更好。然后我就开始使用CEG来构建前端,这样带来了一个超级强壮的HTML5的浏览器组件。

 

Step A: JavaScript is the king of the world! 第一步,JS是王

Our initial idea was to do all the work in JavaScript. 我们初步的想法是,用JS来做所有的事情。

Since we were planning on building the UI using HTML and JavaScript, initially we felt that putting all the business and communications logic in JavaScript also would simplify the architecture and keep it uniform. (The impulse for this type of uniformity between UI and services lies at the heart of platforms like node.js.)

As such, we developed a simple and minimal C++ library allowing JavaScript to invoke remote calls via RTMP and handle asynchronous responses. We kept RTMP because it works well for our current scale, and because we didn’t want to change the communications protocol at the same time as changing the client.

所以,我们打算开发一个简单并小巧的C++库来让JS通过RTMP协议来处理远程调用。保留RTMP是因为我们当下有很多这样的应用。我们不想在更换客户端技术架构的同时修改通讯协议。

Based on some internal prototyping, we decided to use ember.js as our single page application framework, and ember-orbit as our data layer.

然后我们在一些内部原型开发后,决定使用ember.js作为单文档应用程序框架,ember-orbit作为数据层。

 

What could possibly go wrong with something so straightforward? Well, quite a bit as it turned out.这样行不行呢?最后发现问题不少

Our JavaScript code became extremely complex due to the fact that we were handling all the asynchronicity in the web tier. Further, player state was being kept in JavaScript also, meaning that we couldn’t easily minimize down to a low memory footprint.

由于要在web上处理很多异步事情,我们JS代码变得非常复杂。由于玩家状态也保存在JS里,这就意味着我们内存开销是个问题。

So, this architecture solved issue #1, but did nothing for issues #2 or #3. An internal developer satisfaction survey found that developing in this way was less productive than the AIR client. Ouch.

他们发现这种架构下的开发效率还不如Air客户端。

 

Step B: We rediscovered web apps (but on the desktop)! 我们在桌面重新创造了网页应用。

Our next step was to provide microservices in C++, presenting the async game protocol as a set of REST resources.

We started thinking about how normal web applications are designed, and we realized we were missing a middle tier. We built a microservice layer (still running on the player’s desktop) to present the RTMP protocol as REST resources, to insulate the JavaScript from so much async. We used websockets to handle events back to the UI. We called this microservices layer the ”foundation.”

Below is a picture showing the Swagger UI for some of the foundation patcher resources. This arrangement greatly simplified the JavaScript code, and the devs were now able to use standard web techniques.

这个阶段下,做的就是把JS里做的许多事情放到了一个新搞的一个C++层里,从而简化JS层里的事情。

We also discovered an additional benefit of this architecture: when we minimized the client, we were able to kill the CEF UI (and all JavaScript VMs), leaving just the foundation. This was only possible because the foundation keeps canonical state—the entire UI can be reconstituted using GETs. The foundation only takes up around 20mb in memory, which is roughly the size of a few internet cat pictures. No one should ever need to kill the client again after getting in-game (as some players currently do to save memory).

This allowed us to start building towards the vision of an "always available" client, which addresses issue #2. The foundation can bring up a system tray notification, even in the background, to indicate a game invite or a friend request.

说实话,这段技术叙说没看懂,关键是UI这部分内容。不知道在说啥。

 

Step C: Still stepping on toes…

The next evolution was to add an extensibility architecture. 接下来是要加入可扩展性架构。

As teams started developing microservices and features, a new problem arose. These teams were often updating the same code, and colliding with each other.

随着团队开发,新的问题出来了。经常更新同一份代码,导致代码冲突很多。

Let me use an analogy to make the point about extensibility. Imagine if ten people were asked to write a single book. Suppose we asked them all to work on each chapter together (e.g. chapter two: “Playing League as a beginner”). It would be a nightmare because they would literally all be trying to change one another's sentences. Instead, it makes sense to give them each a single chapter to write (e.g. chapter five: “Masteries” and chapter six: “Runes”), allowing them to work independently on the same book. Of course, the challenge now is to make the book read cohesively.

We faced a similar challenge on the client update. Teams were adding their features, but too often they had to adjust shared code and frameworks. We needed an architectural pattern to keep them as independent as possible.

由于大家都在更新同一份代码,当然并行开发就弱了,所以得搞一种框架来分离大家的工作,尽可能的并行工作。

I’ve spent a lot of time working on extensibility architectures and I know how nicely they solve the developer collision problem. To keep our architecture simple we chose a variant on a tried and tested extensibility approach called plugins. A plugin is an independent unit of ownership, development, testing, and deployment. Each plugin respects semver and must indicate which other plugins and versions it depends on, which keeps the architecture explicit. We augmented this style with a fully explicit dependency graph, indicating how all the plugins were connected.

这里使用了一种插件式的开发方式。

So, a UI screen or feature goes into what we call a front-end (FE) plugin, and C++ communications logic goes into a (somewhat confusingly named) back-end (BE) plugin. Note that both types of plugin live inside the new client - we used the terms front and back-end because the architecture mimics a typical client-server architecture, even if it is inside a single desktop process.

We then allow the set of plugins chosen for a player to be based on what entitlements that player has. Entitlements are used to control regional-specific features, and will also be used to give access to new games.

这样,前端的一个功能会封装到一个我们自定义的前端插件里,而C++通信逻辑则封装到了一个我们自定义的后端插件,当然,这两种插件都在客户端里。其实就是在客户端内部,又使用C/S的思想结构。

In the process of moving to this architecture, we had to unpack our monolithic JavaScript data layer. We were using ember-orbit to maintain a single connected graph of objects, representing our REST resources. This was a big source of headaches for us, as it still meant that other teams were able to access each other’s data via object links rather than service calls. This was way too much coupling.

So, we retired our use of Orbit and used a simple resource cache owned by each front-end plugin. Doing all this retired a metric shedload of complexity.

为了应用这种框架,我们开始拆分JS数据层。那么前端的资源管理呢。

 

Step D: Ask N JavaScript developers for their favorite MVC framework. Get N+1 answers.

We allowed each feature team to choose their own JavaScript framework.

We now had a much more productive architecture and teams were able to make screens and services quickly, with clear separation of responsibilities.

However, we had a developer problem. Some of the teams really didn’t like Ember.js and wanted to use web components directly. Other teams already had their own web apps written in a variety of frameworks that they wished to host inside our new client.

At this point we were still reluctant to relax the "Ember.js for all” rule just for developer preference. Removing the assumptions related to Ember as the single embedded framework was going to take some serious work.

The turning point came when we realized that a client platform for a company the size of Riot cannot even force everyone onto the same version of Ember. It was painful, but we ended up architecting the client to allow each plugin to potentially have its own framework—the JavaScript framework became part of the domain of each front-end plugin.

Note that the key word above is potentially. We still use Ember for the majority of our features for consistency purposes, but this is no longer technically required.

这个部分好像就是说FE插件,可以随便搞了,架构是什么样的都无所谓了。作业得看看Ember.js这个东西。

The final architecture

The target architecture for the updated client now addresses our three original issues well. It’s an engine which allows multiple teams to independently deliver HTML5-based features without unnecessary dependencies, using supporting communications infrastructure to allow players to remain connected. It’s effectively a hosting engine for C++ microservices and JavaScript web apps, where the choice of plugins is personalized and dynamic, based on a player’s entitlements.

这个小引擎还是很牛逼的,基本上是个JS网页应用程序,并且和C++层通信,而且呢,支持插件式开发

We repeated the developer satisfaction survey mentioned above, and found that the situation had improved by around 15% and was rising quickly. Nice!

One of the developers commented that we’d made a "datacenter in a desktop." From another perspective, I realized we'd created the game client equivalent of Atom, Github’s CEF-based extensible text editor based on the Electron framework. Interesting stuff, and I would never have guessed that we'd end up here at the start.

这个很像是游戏版本的Atom,一个基于CEF的可扩展的基于Electron框架的文本编辑器。当然刚开始的时候,我们完全没想到会走到这个思路上来。

There are a lot of questions that this post hasn’t explicitly addressed, such as how we can ensure that features interact seamlessly and don't look stitched together, how we get game quality effects using HTML5, and how we allow for the independent deployment of features. These are fascinating areas in their own right, and we're happy to talk more about some of these things if there's a lot of interest. Hit us up in the comments!

 

 

 

 

 

 

 

 

 

 

 

posted @ 2016-07-31 18:47  DesignYourDream  阅读(751)  评论(0)    收藏  举报