├── .gitignore ├── README.md ├── english-1 ├── pdf │ ├── pomelo-design-motivation.pdf │ └── web-yixin-arch.pdf ├── pomelo-design-motivation.md ├── pomelo-framework-overview.md └── word │ └── Pomelo framework-part 1.docx ├── node-game-server-1 ├── img │ ├── broadcast.png │ ├── divide-process.png │ ├── pomelo-runtime-architecture.png │ └── process-area.png ├── node-game-server-1.html └── node-game-server-1.md ├── node-game-server-2 ├── img │ ├── chatDir.png │ ├── chatFlow.png │ ├── game-server-dir.png │ ├── multi-json-architecture.png │ ├── multi_chat.png │ ├── serverAbstraction.png │ ├── single-json-architecture.png │ └── uberchat.png ├── node-game-server-2.html └── node-game-server-2.md ├── node-game-server-3 ├── Pomelo游戏开发-MarkdownPadPreview.html ├── Pomelo游戏开发.md ├── img │ ├── archMuti.png │ ├── archSimple.png │ ├── areaArch.png │ ├── areaRoute.png │ ├── mutiConnectors.png │ ├── pushMsg.png │ ├── reqResp.png │ └── treasures.png └── pomelo游戏开发.html ├── node-game-server-4 ├── img │ ├── TowerAOI.png │ ├── area.png │ ├── pomelo-sync-use.png │ └── pomelo-sync.png └── node-game-server-4.md ├── node-game-server-5 └── node-game-server-5.md └── node ├── img ├── compile_opt.png ├── function_1.png ├── gc_1.png ├── gc_2.png ├── gc_graph_1.png ├── gc_graph_2.png ├── hidden_class_a.png ├── hidden_class_b.png ├── hidden_class_c.png └── tick_1.png └── node.js-V8引擎相关的性能优化.md /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | */node-log.log 3 | logs/*.log 4 | *.log 5 | !.gitignore 6 | node_modules/* 7 | .project 8 | .settings/ 9 | **/*.svn 10 | *.svn 11 | *.swp 12 | *.sublime-project 13 | *.sublime-workspace 14 | lib/doc/ 15 | lib-cov/ 16 | coverage.html 17 | .DS_Store 18 | .idea/* 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | game-server-development 2 | ======================= 3 | 4 | the articles about game server development in node.js 5 | 6 | * [基础架构与框架介绍](./node-game-server-1/node-game-server-1.md) 7 | * [分布式聊天服务器搭建](./node-game-server-2/node-game-server-2.md) 8 | -------------------------------------------------------------------------------- /english-1/pdf/pomelo-design-motivation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/english-1/pdf/pomelo-design-motivation.pdf -------------------------------------------------------------------------------- /english-1/pdf/web-yixin-arch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/english-1/pdf/web-yixin-arch.pdf -------------------------------------------------------------------------------- /english-1/pomelo-design-motivation.md: -------------------------------------------------------------------------------- 1 | # Pomelo framework -- Design motivation 2 | 3 | Initially, pomelo was designed for game server, but after completion of the design and development, it was found that pomelo can be a general-purpose distributed real-time application development framework. Here, we will illustrate pomelo's design motivation by analyzing demands of a game server and problems faced when developing it. 4 | 5 | What's Game Server 6 | ===================== 7 | 8 | People who have not worked on game server may think game server is very mysterious. But in fact it is not more complicated than the web server, it is nothing but provides services for the network requests from the clients. Essentially, it's just a socket server based on long connection. Of course, it demands more than web server on the aspect of logical complexity, message volume, real-time performance, etc. Now, we will introduce you some features of game server by making a contrast between web server and game server: 9 | 10 | ### Complicated Socket Server 11 | 12 | In fact, we can treat a web server as a http server, while we treat a game server as a raw socket server . It is implemented to handle interactions between server-side and client-side by using socket communication. Therefore, there are a lot of game servers are implemented based on the native socket (aka TCP) directly. Compared with simple socket server, the game server does more onerous tasks: 13 | 14 | * Extremely complicated game logics that the backend servers must handle. 15 | * A huge amount of network traffic, and real-time requirement. 16 | * In general, a single socket server can not stand it, so it tends to use a cluster of servers to provide services. 17 | 18 | ### Long Connection and Real-time 19 | 20 | Web application is based on request/response pattern of short connection, so resources holded by the web server is much less than game server because game server uses long connection. Thus, maximum scalability can be achieved for web server by using http-based application to handle short connection. The reasons why web server can use short connection are presented as follows: 21 | 22 | * One-way communication, typical web application only supports pull mode; 23 | * Low demand on real-time, generally, it can be considered timely if web server can respond within 3 seconds. 24 | 25 | But game application can only use long connection for the following reasons : 26 | 27 | * Two-way communication, game application should support both pull mode and push mode, and what's more is that the amount of data server pushes is far greater than the data client pulls initiatively. 28 | * Extremely high demand on real-time, the server should push message to client real-timely, and maximum response time should be less than 100ms. 29 | 30 | ### Partitioning Strategy and Load Balancing 31 | 32 | There is no adjacent concept on interaction between web applications in general, the interaction between all users are equal. The interactive frequency is irrelevant to user's geography in web application, but it is opposite in game application. The interactive frequency with other players in the game is related largely with where the player is located(area). For example, two players who is adjacent to each other can attack each other or make a team to attack monsters, so the interaction between them will be very frequent, and the real-time requirement is very high too, that means it must be required that these two players should be located in the same area server process to reduce cross-process cost. So there will be a partition strategy according to area for game application, that is different from web application, as shown below: 33 | 34 | ![area](https://raw.github.com/wiki/NetEase/pomelo/images/processArea.png) 35 | 36 | A server process can stand one area or multiple areas, so scalability of the game server is limited by area processes. If an area is too busy, out of its capacity, then the whole game server will be blocked and down. Area server is stateful, requests from a particular player must be sent to the same area server. As we know, stateful server brings us many problems, that leads to the area server is not as good as web server on the aspects of high scalablity and high availability. Typically, we had to alleviate these problems by isolating game servers from each others. 37 | 38 | Web application's partitioning can be determined based on regular load balancing, while the game application's is based on the area partitioning strategy, which enables players within a same area to run in the same area server process, in order to reduce cross-process cost. 39 | 40 | ### Scalability and Distributed 41 | 42 | Whether a web application or game application, scalability is always the most important indicator to evaluate it, and it is also the most difficult problem, which involves the running architecture and various optimization strategies. It is assured on the scale of the game, the number of online player at the same time and response time to players only if you have a scalable design. The running architecture of most previous game servers uses a single process model where all the logic is handled, and it is available when there are not many enough players online at the same time. Due to the increasing number of online player at the same time, it brings big challenges to scalability of the single process server inevitably. Therefore, distributed and multi-process game server becomes an inevitable choice. 43 | 44 | Here is figure showing differents between architectures of web server and game server: 45 | 46 | ![game_server_arch](https://raw.github.com/wiki/NetEase/pomelo/images/webGameComp.png) 47 | 48 | It can be seen that web server can redirect requests to any process only according to load balancer, so its running architecture is relatively simple, and rarely require to be distributed. 49 | 50 | But game server uses a spider-net-like architecture, each process has its own responsibility, these processes are intertwined together to complete a task. So game server is a typical distributed architecture. 51 | 52 | Difficulties 53 | ============= 54 | 55 | From the analysis above, we know the game server uses a spider-net-like architecture, and this bring some difficulties to game server development. These difficulties include: 56 | 57 | ### Real-time Guaranteed 58 | 59 | For a game server, its real-time tasks include: 60 | 61 | ##### Real-time Tick 62 | Typically, game server requires a timed tick to perform timed tasks. In order to achieve real-time behavior, this tick timer may be within 100ms. These tasks include the following logics in general: 63 | 64 | * Traverse entities including players, monsters, etc. in an area, and do some operations on them, such as moving , reviving, disappearing. 65 | * Timed producing the monsters in an area as some of them are killed. 66 | * Timed AI logics, such as the monster's attacking , escaping and other logic. 67 | 68 | Because the time limitation is 100ms, so these tasks must be executed in far less than 100ms. 69 | 70 | ##### Broadcast 71 | 72 | Since a player's action in the game must be notified to other players in the same area real-timely, so broadcast is required to do so, that also makes network traffic of game application is much higher than web application. 73 | 74 | Broadcast in the game is expensive. Player's input and output are asymmetrical, for example, a player just makes a little moving, then the server needs to deliver this action to all other players who can see this player in the same area. If there are fewer players inside an area, the number of broadcast messages are not too many, but if the number of players reaches a high level, the number of broadcast messages will increase exponentially. As shown below: 75 | 76 | ![broadcast](https://raw.github.com/wiki/NetEase/pomelo/images/broadcast.png) 77 | 78 | Assuming there are 1000 players in an area, and each one does an action, if server need notify these actions to each others in the area, that means all the player can see each other in the area, then the number of broadcast messages will up to 1,000,000, it is enough to down anything. 79 | 80 | ### Distributed 81 | 82 | Almost in many books, lectures and articles can be seen this point: distributed development is difficult. Its difficulties includes: 83 | 84 | ##### Multi-process(server) Management 85 | 86 | The game server usually use multi-process model. As these processes are intertwined with each other, it becomes difficult to manage these processes. 87 | 88 | Without a unified abstraction and management of the servers(processes), it is a very complicated task to start theses servers up in development environment, the operations of starting and restarting servers will seriously affect the development efficiency. What's more, heavyweight processes consume a lot of machine resources, a regular machine used for development may not stand so many processes, it is possible that a developer requires multiple machines, that will bring us inter-process debugging which is difficult as we know. 89 | 90 | ##### RPC Invocation 91 | 92 | The solution for RPC invocation has existed for many years, but development efficiency is still not improved significantly. 93 | 94 | Here, we use the most popular rpc framework `thrift` as an example to show how to use a rpc framework traditionally. It needs to go through the following steps before writting the invoking code: 95 | 96 | * Write a .thrift file 97 | * Generate some source code from .thrift file by using its compiler: 98 | 99 | ``` 100 | thrift - gen 101 | ``` 102 | 103 | * Use generated source code in application development. 104 | 105 | And if interfaces defined change, we need to modify the .thrift file and redo all the steps above. For development environment in which the interfaces are unstable, it will impact on development efficiency heavily if you do rpc in this way. To make development of the RPC be easier, we need a flxible way without writing interface description file and generating stub interfaces. 106 | 107 | ##### Distributed Transaction & Asynchronous Operation 108 | 109 | Even though we try to put the related logic into one process, distributed transaction is still inevitable. Two-phase commit, asynchronous operation in the development is not an easy task while using an ordinary programming language. 110 | 111 | ##### Load Balancing, High Availability 112 | 113 | Since game server is stateful, requests from a particular player need to be routed to the same server through a specific routing rule, while we can route requests to the least loaded server for stateless web server. Usually it is easier to make it be high available for stateless servers than stateful servers. For stateful servers, making it be high available will be very difficult to do, but also there are some way to do that, here are two approaches: 114 | 115 | * Store states to external storage 116 | 117 | For example, we can use redis or something like to store all the players' states, so that the server does not have to hold any states, it becomes stateless. But because all operations my be through redis, that will leads to performance loss. And in some cases, performance loss can not be beared. 118 | 119 | * Server Redundancy 120 | 121 | The state of server can be synchronized to another redundant server via logs for a backup, but there may be momentary data loss problems while server switching, such data lost does nothing in some application cases, but it may cause severe data inconsistencies in other application cases. 122 | 123 | Stateful high availability is very hard to implement, pomelo v0.5 provides a high availablity mechanism, it introduces zookeeper and redis that can solve the high-availablity problems for some servers(eg master server), but when in actual complex application case logic can only be handled by the application itself. 124 | 125 | ### Issues of Development on Native Socket 126 | 127 | In addition to the above, the development on native socket has lots of issues too: 128 | 129 | * Low Level Abstraction 130 | 131 | Abstraction level of native socket is too low to use, many mechanisms need to be implemeted by the developer, such as session, filter, request, broadcast and so on. it is a heavy work and error-prone, and in fact it will do a lot of duplication of effort that every server will do. 132 | 133 | * Scalability 134 | 135 | Scalability depends on many aspects, including the message density, storage policies, server architecture and other factors. With the native socket to achieve high scalability, developer needs to spend a lot of effort on architecture design. 136 | 137 | * Server Monitoring and Management 138 | 139 | It is necessary to monitor the state of the servers, such as message density , online player number, machine status, network pressure and so on. It is a big work if you use the native socket to do so. 140 | 141 | Framework-based Solution 142 | ========================== 143 | 144 | Yes, we need a framework to simplify the development of game server. Except the game logic itself, most of work can be done by a framework. server abstraction, scalability , extensibility, these problems can be addressed by the framework to avoid duplicate efforts. Also, game server framework can do some work as an application server framework, you can also treat the framework as a container, as long as throwing the code met its specification into the container, then it runs. Meanwhile, the code has the ability of abstraction, scalability, and monitoring & management capabilities naturally that the framework provides. 145 | 146 | ### Existed Game Server Frameworks 147 | 148 | There are countless web server frameworks in open source community, so is the game client framework and library, but there is few game server framework. Maybe there are some sporadic libraries, but almost no complete solution. So we had to take some commercial solutions to make a comparsion: 149 | 150 | * Sun RedDwarf 151 | 152 | RedDwarf is only one that we have found, which is a open source game server framework produced by Sun Inc. Unfortunately, it has be stopped since Sun was merged into Oracle. RedDwarf uses distributed architecture, and it invests too many efforts into distributed data storage and task management, it is so idealistic that it is very complex, for example, its implementation of dynamic task migration is very complex, but it acts a rare role in practical applications. Scalability and performance is not good too, so RedDwarf died. 153 | 154 | * SmartfoxServer 155 | 156 | SmartfoxServer is produced by gotoAndPlay() from Italy, and it is a commercial game server. It is written in java and looks like a web application server such as Tomcat. Smartfox supports a variety of client platforms, and there are some successful application cases. Its implementation of server abstraction and monitoring & management of servers is perfect. But it behaves not well on scalability, although Smartfox also supports cluster mode, but it is based on jvm memory replication. It doesn't support traditional MMORPG partitioning solution. Smartfox has free version, but it is not open-source-ed. And its free version (limit on number of online users) aims to attract developers to buy its paid version. A paid version (no limit on online users) price has reached $3500. 157 | 158 | * BigWorld 159 | 160 | Bigworld is produced by Bigworld Technology from Australia. It is a full 3D MMORPG game development solution, including client side and server side. Bigworld is very powerful, it does a lot of work on dynamic load balancing and fault tolerance. Scalability is also very powerful. Its disadvantage is too heavyweight and the price is very expensive. Bigworld is designed for large 3D MMORPG game, but it is not suitable to small and medium sized game development. 161 | 162 | ### Pomelo Solution 163 | 164 | With situation of market of current game server framework that there is no suitable framework for small and medium game development, we launched the pomelo framework, which is an open source framework using the MIT open source license based on node.js. It aims to provide a high-performance , scalable, lightweight game server framework. Actually, it solves the difficulties in game server development and makes game server development more easier. Comparing to other similar frameworks, its main advantages are shown as follows: 165 | 166 | * Development on pomelo is rapid and approachable based on the principle of convention over configuration, that makes the code to be simplifed. 167 | * High scalability and extensibility the framework architecture provides makes it be very convenient to scale and extend the applications. 168 | * Lightweight, although pomelo has a distributed architecture, it can start very quickly with little resource requiring. 169 | * Comprehensive reference, pomelo provides not only complete documentation, but also a complete MMO demo (client uses HTML5), which can serve as a powerful reference to developer. 170 | 171 | ##### Why Node.js 172 | 173 | After talking about so many difficulties in distributed development, it is well-reasoned to introduce node.js. it can solve many difficulties in distributed development because of its native asynchronous programming model: 174 | 175 | * Native Distribution 176 | 177 | The reason why node.js was called node is that it supports multi-process development model natively, and multiple nodes(processes) communicate and intertwine with each other can be a distributed system. Its programming mode is asynchronous, two-phase commit for transaction, asynchronous operation and other works that seems complex are supported natively in node.js. 178 | 179 | * Single-threaded Application Model 180 | 181 | Single-threaded application model node.js provides is powerful than other languages, and it is most simplest to handle the game logic using single-threaded model with avoiding the deadlock and lock race, and it is not error-prone. 182 | 183 | * Network IO & Scalability 184 | 185 | Game servers are IO-intensive applications, so node.js is the most suitable as it uses an event-driven, non-blocking IO model. Node.js introduces high level abstraction for network programming that makes it easy to do network programming. Meanwhile, it is easy to build a scalable application using node.js. 186 | 187 | * Language Advantage 188 | 189 | Javascript is becoming an important language now, Developing in Javascript can obtain rapid iteration. Its simplicity and lightweight can increase development efficiency. Also, Sharing source code between server-side and client-side comes to be possible for some kind of the client platforms, such as html5 or unity3d which supports javascript for client development. In addition, dynamic typing javascript provides can bring a lot of convenience for framework design, such as design of DSL and implementing convention over configuration. Although it is somewhat not better than Ruby, it is good enough to use in the pomelo framework. 190 | 191 | From Game to Real-time Applications 192 | =================================== 193 | 194 | After finising the analysis of design goals of pomelo, we find that the core framework is not specific to game server but a general real-time application framework. The demo chat provided is a real-time application. 195 | Indeed, pomelo has been used in many non-gaming field. The message push server produced by Netease is developed based on pomelo, which supports the message pushing to mobile clients and web clients for some Apps produced by Netease, and now it has been on-line. 196 | 197 | Summary 198 | ========== 199 | 200 | In this section, we have analyzed the characteristics of game server first, and conclude it is more difficult to develope a game server than web server because of its complexity. We present deficiencies of some existed game server framework and explain how pomelo to solve it. We also introduce design motivation and goals of pomelo simply. The Next is [pomelo framework overview] (Pomelo-framework-overview "pomelo Framework Overview"). 201 | 202 | -------------------------------------------------------------------------------- /english-1/pomelo-framework-overview.md: -------------------------------------------------------------------------------- 1 | # Pomelo framework Overview 2 | 3 | The runtime architecture of a scalable game server must be multi-process, since single process can not be scaled easily. [Gritsgame] (http://code.google.com/p/gritsgame/) from google and [Browserquest] (https://github.com/mozilla/BrowserQuest) from mozilla both use node.js as platform for game server, but they use a single-process model, which means their number of online users are limited and lack of scalability. The multi-process architecture can obtain high scalability for game server, 4 | 5 | Typical Multi-process Architecture 6 | ==================================== 7 | 8 | A typical runtime architecture of a multi-process MMO game server is shown as follows: 9 | 10 |
11 | ![MMO running Architecture](https://raw.github.com/wiki/NetEase/pomelo/images/mmo-arch.png) 12 |
13 | 14 | Some Notes about the figure above: 15 | 16 | * Every rectangle in the figure represents a process, that is equivalent to the "server" conceptually; 17 | * Clients connect to the `connector` server via websocket or socket; 18 | * Connectors that are treated as frontend server. They do not do any game logic, they just forwards the requests from clients to backend servers; 19 | * Backend servers including area, chat, status and other type servers are responsible for their own business logic. There may be many other type servers in actual cases. 20 | * Backend servers will send back response of logic handling to connectors, then connectors broadcast/respond/push it back to clients; 21 | * Master server manages all these servers, including startup, monitoring and stop etc. 22 | 23 | Introduction of Pomelo Framework 24 | =================================== 25 | 26 | #### Components of Pomelo Framework 27 | 28 | Pomelo framework's components is as below: 29 | 30 |
31 | ![pomelo framework] (images/pomelo-arch.png) 32 |
33 | 34 | * Server Management 35 | 36 | pomelo uses multi-process model, so it is especially important to manage the servers. The abstraction of servers in the framework makes server management very easy; 37 | 38 | * Network 39 | 40 | Netwok in pomelo framewrork can be divided into two parts: communication between frontend servers and clients, communication inside server cluster. It includes request/response, broadcast, session management, RPC invocation,and all these communications construct the game logic flow . 41 | 42 | * Application 43 | 44 | This is crucial for a loosely coupled architecture, application is treated as a global context to support components' life cycle, app DSL, etc., which make pomelo framework pluggable and easy to extend. 45 | 46 | Design Goals 47 | ============ 48 | 49 | * Abstraction for Servers(Processes) 50 | 51 | In web applications, servers are stateless, loosely coupled, in other word, there is no need to manage all these servers with a framework. 52 | However, the game server are different from web. A particular game may includes various server types and various amount of a particular server type. All these require framework to support abstraction and decoupling of servers. 53 | 54 | * Abstraction for Request/Response, Broadcast 55 | 56 | Requests/response is similar in game application comparing with web application, but game is based on long connection, so a common request/broadcast mechanism is required. Since broadcast is the most frequent action in game application, so the framework needs to provide a convenient api and make it as efficient as possible. 57 | 58 | * RPC Invocation between Servers 59 | 60 | Servers need to talk to each other, although we try to avoid it, inter-process communication is inevitable and therefore it requires a user-friendly rpc framework. 61 | 62 | * Loosely Coupled, Pluggable Application. 63 | 64 | Application exptension is very important, pomelo framework supports extension, you can customize your own components, routing rules , request filter, admin-module and then plug it into pomelo framework. 65 | 66 | ### Abstraction for Servers 67 | #### Server Types 68 | 69 | Pomelo divides servers into two categories: frontend server and backend server, as shown below: 70 | 71 |
72 | ![Server abstract] (images/serverAbst.png) 73 |
74 | 75 | The responsibilities of frontend servers: 76 | * Handle connection from clients 77 | * Maintain session information for clients 78 | * Forward requests from clients to backend servers 79 | * Broadcast/respond messages produced by backend servers to clients 80 | 81 | The responsibilities of backend servers: 82 | * Handle business logic, including RPC requests and requests forwarded by frontend servers 83 | * Push response meessages to frontend servers. 84 | 85 | #### Server Duck Type 86 | 87 | Duck type is a concept commonly used in OOP for dynamic programming language. However, it can also be used to make server abstraction. There are only two types of interfaces for a server, one is to handle requests from clients, called "Hanldler", and another one is to handle rpc invocation, called "Remote". 88 | 89 | So long as we define remote and handler for a server, we can determine type of the server and what it looks like. 90 | 91 | #### Implementation of Server Abstraction 92 | 93 | The simplest way is to make the orgnization of server code correspond with directory structure. Here is a example: 94 | 95 |
96 | ![Directory structure] (images/serverAbsDir.png) 97 |
98 | 99 | We just design a server called "area", the behavior of the server is determined by the code in "handler" and "remote". 100 | All we need to do is to fill code into handler and remote. 101 | 102 | To make the server run, we need a small config file called servers.json. Here is a example as follows:  103 | 104 | ```json 105 | { 106 | "development": { 107 | "connector": [ 108 | {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true}, 109 | {"id": "connector-server-2", "host": "127.0.0.1", "port": 3151, "clientPort": 3011, "frontend": true} 110 | ] , 111 | "area": [ 112 | {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "area": 1}, 113 | {"id": "area-server-2", "host": "127.0.0.1", "port": 3251, "area": 2}, 114 | {"id": "area-server-3", "host": "127.0.0.1", "port": 3252, "area": 3} 115 | ] , 116 | "chat": [ 117 | {"id": "chat-server-1", "host": "127.0.0.1", "port": 3450} 118 | ] 119 | } 120 | } 121 | ``` 122 | 123 | ### Request/Response, Broadcast 124 | 125 | Although we use long connection in game, but the request/response API is similar to web. Here is a example: 126 |   127 |
128 | ![Request Sample] (https://raw.github.com/wiki/NetEase/pomelo/images/req-resp.png) 129 |
130 | 131 | The API of request/response looks much like "Ajax", although it uses a long connection actually. Based on the principle "convention over configuration", there is no need to config anything. As shown above, the request route string: "chat.chatHandler.send" is composed by serverTpe, handler, and request Method. 132 | 133 | pomelo also provides filter, broadcast/multicast mechanism, and channel support, etc. 134 | 135 | ### RPC Invocation Abstraction 136 | 137 | The rpc framework pomelo provides is really simple rpc framework. It can automatically choose route rules and route invocations to the target server without any configuration. Here is a example: 138 | 139 |
140 | ![rpc call] (https://raw.github.com/wiki/NetEase/pomelo/images/rpc.png) 141 |
142 | 143 | There is a interface defined in the figure above: chatRemote.js, and is defined as follows : 144 | 145 | ```javascript 146 | chatRemote.kick = function (uid, player, cb) { 147 | } 148 | ``` 149 | The RPC client can emit a RPC invoke just like this : 150 | ```javascript 151 | app.rpc.chat.chatRemote.kick (session, uid, player, function (data) { 152 | }) ; 153 | ``` 154 | Notice the parameter, "session", it is used for routing requests. The framework routes invocations to certain server automatically based on route rule. For example, an invocation for "area" service will be forwarded based on which area is the player in. RPC framework is transparent to the developer and pomelo provides a friendly rpc API for developers. 155 | 156 | ### Pluggable Component 157 | 158 | Component is pluggable module in pomelo, developers can implement their own component and just plug it into pomelo. The core functionalities of pomelo are implemented by its builtin components. In other word, it can be said pomelo framework is just a container for its components. 159 | 160 | The life cycle of components are shown as follows: 161 | 162 |
163 | ![components] (https://raw.github.com/wiki/NetEase/pomelo/images/components.png) 164 |
165 | 166 | All developers have to do is implementing all these interfaces: start, afterStart, stop, and then we can load it in app.js by: 167 | 168 | ```javascript 169 | app.load ([name], comp, [opts]) 170 | ``` 171 | 172 | Further more, pomelo also support plugin, which is based on component. Plugin is an independent npm module. With plugin, programmers can extend the framework very easy without any influences to the framework core. Pomelo provides some built-in plugin as follows: 173 | 174 | + [pomelo-sync-plugin](https://github.com/NetEase/pomelo-sync-plugin):data synchronization plugin, based on [pomelo-sync](https://github.com/NetEase/pomelo-sync). 175 | + [pomelo-globalchannel-plugin](https://github.com/NetEase/pomelo-globalchannel-plugin):gloableChannel plugin, based on redis. 176 | + [pomelo-status-plugin](https://github.com/NetEase/pomelo-status-plugin):user online status plugin, based on redis. 177 | + [pomelo-masterha-plugin](https://github.com/NetEase/pomelo-masterha-plugin):master availability plugin, based on zookeeper. 178 | 179 | 180 | 181 | Summary 182 | ========== 183 | 184 | In this section, we describe overall architecture of the pomelo framework, as well as its design goals. Pomelo framework implements abstractions of server, request/response, rpc and pluggable component. These abstractions make pomelo very flexible and easy to use, easy to extend. 185 | -------------------------------------------------------------------------------- /english-1/word/Pomelo framework-part 1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/english-1/word/Pomelo framework-part 1.docx -------------------------------------------------------------------------------- /node-game-server-1/img/broadcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-1/img/broadcast.png -------------------------------------------------------------------------------- /node-game-server-1/img/divide-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-1/img/divide-process.png -------------------------------------------------------------------------------- /node-game-server-1/img/pomelo-runtime-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-1/img/pomelo-runtime-architecture.png -------------------------------------------------------------------------------- /node-game-server-1/img/process-area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-1/img/process-area.png -------------------------------------------------------------------------------- /node-game-server-1/node-game-server-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 147 | 深入浅出node.js游戏服务器开发1---基础架构与框架介绍 148 | 149 | 150 | 151 |

深入浅出node.js游戏服务器开发1---基础架构与框架介绍

152 | 153 |

本系列文章将分别介绍用node.js进行游戏服务器开发、游戏的基础架构、游戏服务器框架pomelo、基础的游戏服务器开发,以及相关的一些开发策略和性能优化的内容。不使用node.js与pomelo开发游戏服务器或高实时web应用,也能从架构、思考和一些策略中有所获。

154 | 155 |

计划的系列文章:

156 | 157 | 167 | 168 | 169 |

游戏服务器概述

170 | 171 |

没开发过游戏的人会觉得游戏服务器是很神秘的东西。但事实上它并不比web服务器复杂,无非是给客户端提供网络请求服务,本质上它只是基于长连接的socket服务器。当然在逻辑复杂性、消息量、实时性方面有更高的要求。

172 | 173 |

游戏服务器是复杂的socket服务器。

174 | 175 |

如果说web服务器的本质是http服务器,那么游戏服务器的本质就是socket服务器。 176 | 它利用socket通讯来实现服务器与客户端之间的交互。事实上有不少游戏是直接基于原生socket来开发的。 177 | 相对于简单的socket服务器,它承受着更加烦重的任务:

178 | 179 | 184 | 185 | 186 |

为什么纯粹的socket服务器还不够好?

187 | 188 |

很多web应用不会基于原生的http服务器搭建,一般都会基于某类应用服务器(如tomcat)搭建,而且还会利用一些开发框架来简化web开发。 189 | 同样,一般游戏服务器的开发都会在socket服务器上封装出一套框架或类似的应用服务器。为什么使用原生的socket接口开发不够好呢?

190 | 191 | 196 | 197 | 198 |

用框架来简化游戏服务器开发

199 | 200 |

一个好的框架可以大大简化游戏服务器的工作。除了游戏自身的逻辑外,大部分的工作都可以用框架来解决。服务端的抽象,可伸缩性,可扩展性这些问题都可以通过框架来解决。 201 | 游戏服务器框架也承担了应用服务器的功能。可以把框架看成容器,只要把符合容器标准的代码扔进去,容器就运行起来了。它自然具备了抽象能力、可伸缩性和监控、管理等能力。

202 | 203 |

游戏服务器框架介绍

204 | 205 |

在开源社区里充斥了数不清的web服务器框架,游戏客户端的框架和库也有一大堆,但唯独游戏服务器框架少之又少,零星有一些类库,但完整的解决方案几乎没有。我们只好从商用的解决方案中拿出一些框架进行类比:

206 | 207 |

Sun RedDwarf

208 | 209 |

RedDwarf是唯一一个能找到的完整的开源游戏服务器框架,由sun出品。可惜在它合并到Oracle以后已经停止开发了。 210 | 在设计上,RedDwarf是个分布式架构,它在分布式数据存储和任务管理上投入了太多精力,而且做的过于理想化,如动态任务迁移功能的实现非常复杂,但实际应用中根本用不到。而在可伸缩性和性能的设计上不太理想。因此RedDwarf夭折了。

211 | 212 |

SmartfoxServer

213 | 214 |

SmartfoxServer是由意大利的一家游戏公司gotoAndPlay()推出的商用游戏服务器。 215 | 它是基于java开发的,与web应用服务器如Tomcat看上去很类似。Smartfox支持各种客户端,且有一些成功案例。它在服务端封装和监控管理方面实现得很完善。 216 | 但在可伸缩性上并不是太理想,尽管Smartfox也支持Cluster模式,但它的扩展方式是基于jvm内存复制的。也没有实现传统MMORPG基于场景分区的解决方案。 217 | Smartfox有免费版本,但完全不开源。而且它的免费版本(达不到高并发用户要求)很大程度是为了吸引开发者最终购买它的收费版本。不限在线人数的收费版本价格达到3500美刀。

218 | 219 |

BigWorld

220 | 221 |

Bigworld是澳大利亚Bigworld公司开发的全套3d MMORPG游戏解决方案,解决方案包含了客户端和服务端。Bigworld功能非常强大,在动态负载均衡和容错性做了很多工作。可扩展性非常强大。 222 | 它的缺点是过于重量级,对硬件要求高,且价格非常昂贵。Bigworld是专门为3d MMORPG游戏定制,但并不适用于中小型游戏的开发。

223 | 224 |

Pomelo

225 | 226 |

Pomelo是网易于2012年11月推出的开源游戏服务器。它是基于node.js开发的高性能、可伸缩、轻量级游戏服务器框架。 227 | 它的主要优势有以下几点:

228 | 229 | 235 | 236 | 237 |

Pomelo目前的主要缺点是推出时间尚短,一些功能还在完善中,支持的客户端类型还有限,目前已支持HTML5、ios、android、untiy3d等4类客户端,未来还会支持更多的客户端类型。

238 | 239 |

游戏服务器的可伸缩性探讨

240 | 241 |

不管是web应用还是游戏服务器,可伸缩性始终是最重要的指标,也是最棘手的问题,它涉及到系统运行架构的搭建,各种优化策略。 242 | 只有把可伸缩性设计好了,游戏的规模、同时在线人数、响应时间等参数才能得到保证。

243 | 244 |

为什么游戏服务器的可伸缩性远远不及web?

245 | 246 |

相比web应用几乎无限扩展的架构(前提是架构设计得好),游戏服务器的可伸缩性相比就着差远了。那么是哪些因素导致游戏无法达到web应用的扩展能力呢? 247 | 说明:本文提到的web应用不包括类似于聊天这样的高实时web应用,高实时web可认为是一种逻辑较简单的游戏。

248 | 249 |

长连接和响应实时性

250 | 251 |

web应用都是基于request/response的短连接模式。占用的资源要比一直hold长连接的游戏服务器要少很多。Web应用能使用短连接模式的原因如下:

252 | 253 | 257 | 258 | 259 |

而游戏应用只能使用长连接,原因如下:

260 | 261 | 265 | 266 | 267 |

在高并发长连接服务的解决方案中,目前除了传统的C语言(过于重量级)实现,用的最多的是erlang与node.js。两者的性能指标差不多,而node.js在易用性方面毫无疑问胜出太多。

268 | 269 |

最近微博上看到时go的能撑起100万的并发连接,node.js也能达到同样的数据, Node.js w/1M concurrent connections!有node.js的长连接数据,它占用了16G内存,但CPU还远没跑满。

270 | 271 |

交互的相邻性与分区策略

272 | 273 |

普通的web应用在交互上没有相邻性的概念,所有用户之间的交互都是平等,交互频率也不受地域限制。 274 | 而游戏则不然,游戏交互跟玩家所在地图(场景)上的位置关系非常大,如两个玩家在相邻的地方可以互相PK或组队打怪。这种相邻的交互频率非常高,对实时性的要求也非常高,这就必须要求相邻玩家在分布在同一个进程里。 275 | 于是就有了按场景分区的策略,如图所示:

276 | 277 |

process area

278 | 279 |

一个进程里可以有一个场景,也可以有多个场景。这种实现带来了以下问题:

280 | 281 | 285 | 286 | 287 |

广播

288 | 289 |

游戏中广播的代价是非常大的。玩家的输入与输出是不对等的,玩家自己简单地动一下,就需要将这个消息实时推送给所有看到这个玩家的其他玩家。 290 | 假如场景里面人较少,广播发送的消息数还不多,但如果人数达到很密集的程度,则广播的频度将呈平方级增长。如图所示:

291 | 292 |

broadcast

293 | 294 |

假如场景中1000个玩家,每人发1条消息,如果需要其它玩家都看到的话,消息的推送量将高达1,000,000条,这足以把任何服务器撑爆。

295 | 296 |

解决这个问题的方案:

297 | 298 | 302 | 303 | 304 |

divide-process

305 | 306 |

这样广播逻辑与具体的进程逻辑就不会相互影响了,而且由于只有后端的场景服务器是有状态的,前端负责广播的服务器还是无状态的,因此前端服务器可以无限扩展。

307 | 308 |

实时Tick

309 | 310 |

实时游戏的服务端一般都需要一个定时tick来执行定时任务,为了游戏的实时性,一般要求这个tick时间在100ms之内。这些任务包括以下逻辑:

311 | 312 | 317 | 318 | 319 |

由于实时100ms的限制,这个实时tick的执行时间必须要远少于100ms,因此单进程内很多数据都会受到限制。

320 | 321 | 327 | 328 | 329 |

高可伸缩的运行架构

330 | 331 |

经过以上这些分析。我们可以得到现在的运行架构,如下图:

332 | 333 |

runtime architecture

334 | 335 |

运行架构说明:

336 | 337 | 344 | 345 | 346 |

这个运行架构符合了刚才提到的几个伸缩性原则:

347 | 348 | 353 | 354 | 355 |

前面提到4个游戏服务器框架,只有bigworld和pomelo符合这样的架构,当然bigworld实现的还要更复杂。 356 | 现在的问题是,这个运行架构是个分布式架构,而且并不简单,那就带来以下问题:

357 | 358 | 365 | 366 | 367 |

node.js、pomelo与游戏服务器

368 | 369 |

Node.js的特点与游戏服务器极其符合。列举如下:

370 | 371 | 377 | 378 | 379 |

Pomelo是基于node.js搭建的游戏服务器框架,它在灵活性、扩展能力,轻量级调试方面具有无可比拟的优势。我们先简单回答第三章最末的几个问题:

380 | 381 | 387 | 388 | 389 |

在本系列文章后面将会陆续讨论pomelo是怎么实现以上如此方便的特性, 以及这些设计带来的启发。

390 | 391 |

小结

392 | 393 |

本文分析了游戏服务器框架的市场现状,一个高可伸缩游戏服务器架构的设计原则及运行架构。Node.js与pomelo在解决高并发和分布式架构中起到的作用。下文我们将深入分析pomelo在解决复杂的游戏服务器运行架构中提供了哪些便利。

394 | 395 | -------------------------------------------------------------------------------- /node-game-server-1/node-game-server-1.md: -------------------------------------------------------------------------------- 1 | #深入浅出node.js游戏服务器开发1---基础架构与框架介绍 2 | 3 | 本系列文章将分别介绍用node.js进行游戏服务器开发、游戏的基础架构、游戏服务器框架pomelo、基础的游戏服务器开发,以及相关的一些开发策略和性能优化的内容。不使用node.js与pomelo开发游戏服务器或高实时web应用,也能从架构、思考和一些策略中有所获。 计划的系列文章: 4 | * 基础架构与框架介绍 5 | * pomelo架构概览 6 | * 用pomelo搭建聊天服务器 7 | * 完整游戏服务器的架构搭建 8 | * 深入游戏服务器场景管理 9 | * 场景管理相关策略---寻路与AOI 10 | * 谈游戏服务器的相关策略--存储、AI 11 | * node.js游戏服务器性能优化实践 12 | 13 | ##游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的东西。但事实上它并不比web服务器复杂,无非是给客户端提供网络请求服务,本质上它只是基于长连接的socket服务器。当然在逻辑复杂性、消息量、实时性方面有更高的要求。 14 | ###游戏服务器是复杂的socket服务器。 如果说web服务器的本质是http服务器,那么游戏服务器的本质就是socket服务器。 它利用socket通讯来实现服务器与客户端之间的交互。事实上有不少游戏是直接基于原生socket来开发的。 相对于简单的socket服务器,它承受着更加烦重的任务: * 后端承载着极复杂的游戏逻辑。 * 网络流量与消息量巨大,且实时性要求极高。 * 通常一台socket服务器无法支撑复杂的游戏逻辑,因此在socket服务器的背后还有一个服务器群。 ###为什么纯粹的socket服务器还不够好? 很多web应用不会基于原生的http服务器搭建,一般都会基于某类应用服务器(如tomcat)搭建,而且还会利用一些开发框架来简化web开发。 同样,一般游戏服务器的开发都会在socket服务器上封装出一套框架或类似的应用服务器。为什么使用原生的socket接口开发不够好呢? * 抽象程度。原生的socket抽象程度过低,接口过于底层,很多机制都需要自己封装,如Session、filter、请求抽象、广播等机制都要自已实现,工作量很大,容易出错,且有很多的重复劳动。 * 可伸缩性。高可伸缩性需考虑很多问题,消息密度、存储策略、进程架构等因素都需要考虑。用原生的socket要达到高可伸缩性,需要在架构上花费大量的功夫,而且效果也未必能达到开源框架的水准。 * 服务端的监控、管理。很多服务器的数据需要监控,例如消息密度、在线人数、机器压力、网络压力等,如果采用原生socket,所有这些都要自己开发,代价很大。 ###用框架来简化游戏服务器开发 一个好的框架可以大大简化游戏服务器的工作。除了游戏自身的逻辑外,大部分的工作都可以用框架来解决。服务端的抽象,可伸缩性,可扩展性这些问题都可以通过框架来解决。 游戏服务器框架也承担了应用服务器的功能。可以把框架看成容器,只要把符合容器标准的代码扔进去,容器就运行起来了。它自然具备了抽象能力、可伸缩性和监控、管理等能力。 15 | ## 游戏服务器框架介绍 在开源社区里充斥了数不清的web服务器框架,游戏客户端的框架和库也有一大堆,但唯独游戏服务器框架少之又少,零星有一些类库,但完整的解决方案几乎没有。我们只好从商用的解决方案中拿出一些框架进行类比: ###[Sun RedDwarf](http://www.reddwarfserver.org) RedDwarf是唯一一个能找到的完整的开源游戏服务器框架,由sun出品。可惜在它合并到Oracle以后已经停止开发了。 在设计上,RedDwarf是个分布式架构,它在分布式数据存储和任务管理上投入了太多精力,而且做的过于理想化,如动态任务迁移功能的实现非常复杂,但实际应用中根本用不到。而在可伸缩性和性能的设计上不太理想。因此RedDwarf夭折了。 ###[SmartfoxServer](http://www.smartfoxserver.com/) SmartfoxServer是由意大利的一家游戏公司gotoAndPlay()推出的商用游戏服务器。 它是基于java开发的,与web应用服务器如Tomcat看上去很类似。Smartfox支持各种客户端,且有一些成功案例。它在服务端封装和监控管理方面实现得很完善。 但在可伸缩性上并不是太理想,尽管Smartfox也支持Cluster模式,但它的扩展方式是基于jvm内存复制的。也没有实现传统MMORPG基于场景分区的解决方案。 Smartfox有免费版本,但完全不开源。而且它的免费版本(达不到高并发用户要求)很大程度是为了吸引开发者最终购买它的收费版本。不限在线人数的收费版本价格达到3500美刀。 ###[BigWorld](http://www.bigworldtech.com/zh/) Bigworld是澳大利亚Bigworld公司开发的全套3d MMORPG游戏解决方案,解决方案包含了客户端和服务端。Bigworld功能非常强大,在动态负载均衡和容错性做了很多工作。可扩展性非常强大。 它的缺点是过于重量级,对硬件要求高,且价格非常昂贵。Bigworld是专门为3d MMORPG游戏定制,但并不适用于中小型游戏的开发。 16 | ###[Pomelo](http://pomelo.netease.com) Pomelo是网易于2012年11月推出的开源游戏服务器。它是基于node.js开发的高性能、可伸缩、轻量级游戏服务器框架。 它的主要优势有以下几点: 17 | * 开发模型快速、易上手,基于Convention over configuration的原则,让代码达到最大的简化。 * 架构的可伸缩性和可扩展性好,pomelo在服务器扩展和应用扩展上实现得非常方便。 * 轻量级,虽然是分布式架构,但启动非常迅速,占用资源少。 * 参考全面,框架不仅提供了完整的中英文档,还提供了完整的MMO demo代码(客户端html5),可以作为很好的开发参考。 Pomelo目前的主要缺点是推出时间尚短,一些功能还在完善中,支持的客户端类型还有限,目前已支持HTML5、ios、android、untiy3d等4类客户端,未来还会支持更多的客户端类型。 18 | ##游戏服务器的可伸缩性探讨 不管是web应用还是游戏服务器,可伸缩性始终是最重要的指标,也是最棘手的问题,它涉及到系统运行架构的搭建,各种优化策略。 只有把可伸缩性设计好了,游戏的规模、同时在线人数、响应时间等参数才能得到保证。 ###为什么游戏服务器的可伸缩性远远不及web? 相比web应用几乎无限扩展的架构(前提是架构设计得好),游戏服务器的可伸缩性相比就着差远了。那么是哪些因素导致游戏无法达到web应用的扩展能力呢? 说明:本文提到的web应用不包括类似于聊天这样的高实时web应用,高实时web可认为是一种逻辑较简单的游戏。 19 | ####长连接和响应实时性 web应用都是基于request/response的短连接模式。占用的资源要比一直hold长连接的游戏服务器要少很多。Web应用能使用短连接模式的原因如下: 20 | * 通讯的单向性,普通web应用一般只有拉模式 * 响应的实时性要求不高,一般web应用的响应时间在3秒以内都算响应比较及时的。 而游戏应用只能使用长连接,原因如下: 21 | * 通讯的双向性,游戏应用不仅仅是推拉模式,而且推送的数据量要远远大于拉的数据量 * 响应的实时性要求极高,一般游戏应用要求推送的消息实时反映,而实时响应的最大时间是100ms。 在高并发长连接服务的解决方案中,目前除了传统的C语言(过于重量级)实现,用的最多的是erlang与node.js。两者的性能指标差不多,而node.js在易用性方面毫无疑问胜出太多。 22 | 最近微博上看到时go的能撑起100万的并发连接,node.js也能达到同样的数据, [Node.js w/1M concurrent connections!](http://blog.caustik.com/2012/08/19/node-js-w1m-concurrent-connections/)有node.js的长连接数据,它占用了16G内存,但CPU还远没跑满。 ####交互的相邻性与分区策略 普通的web应用在交互上没有相邻性的概念,所有用户之间的交互都是平等,交互频率也不受地域限制。 而游戏则不然,游戏交互跟玩家所在地图(场景)上的位置关系非常大,如两个玩家在相邻的地方可以互相PK或组队打怪。这种相邻的交互频率非常高,对实时性的要求也非常高,这就必须要求相邻玩家在分布在同一个进程里。 于是就有了按场景分区的策略,如图所示: ![process area](img/process-area.png) 一个进程里可以有一个场景,也可以有多个场景。这种实现带来了以下问题: 23 | * 游戏的可伸缩性受到场景进程的限制,如果某个场景过于烦忙可能会把进程撑爆,也就把整个游戏撑爆。 * 场景服务器是有状态的,每个用户请求必须发回原来的场景服务器。服务器的有状态带来一系列的问题:场景进程的可伸缩,高可用性等都比不上web服务器。目前只能通过游戏服务器的隔离来缓解这些问题。 24 | ####广播 25 | 游戏中广播的代价是非常大的。玩家的输入与输出是不对等的,玩家自己简单地动一下,就需要将这个消息实时推送给所有看到这个玩家的其他玩家。 假如场景里面人较少,广播发送的消息数还不多,但如果人数达到很密集的程度,则广播的频度将呈平方级增长。如图所示: ![broadcast](img/broadcast.png) 假如场景中1000个玩家,每人发1条消息,如果需要其它玩家都看到的话,消息的推送量将高达1,000,000条,这足以把任何服务器撑爆。 解决这个问题的方案: 26 | * 减少消息数量---消息只发送给能看到的玩家。玩家能看到的只是屏幕的大小,而不是整张地图的大小,这样推送消息的时候可以只推给对自己的状态感兴趣的玩家。这个可以用AOI(area of interested)算法来实现,在pomelo的库pomelo-aoi中实现了简单的灯塔算法。 * 分担负载,将消息推送的进程与具体的逻辑进程分离。如图: 27 | ![divide-process](img/divide-process.png) 这样广播逻辑与具体的进程逻辑就不会相互影响了,而且由于只有后端的场景服务器是有状态的,前端负责广播的服务器还是无状态的,因此前端服务器可以无限扩展。 ####实时Tick 28 | 实时游戏的服务端一般都需要一个定时tick来执行定时任务,为了游戏的实时性,一般要求这个tick时间在100ms之内。这些任务包括以下逻辑: 29 | * 遍历场景中的实体(包括玩家、怪物等),进行定时操作,如移动、复活、消失等逻辑。 * 定期补充场景中被杀掉的怪的数量。 * 定期执行AI操作,如怪物的攻击、逃跑等逻辑。 由于实时100ms的限制,这个实时tick的执行时间必须要远少于100ms,因此单进程内很多数据都会受到限制。 30 | * 场景内实体的数量受限制,因为要遍历所有实体 * 注意更新的算法,所有的算法,包括AI在内都要在几十毫秒全部完成 * 注意GC,full GC最好永远不要发生。一般full GC的时间都会高于100ms,幸好node.js在内存少于500M时表现良好,只有小GC。因此一定要控制内存大小。 * 尽量分进程,进程的粒度越少,出现tick超时或full GC的可能越少。在多核时代里,CPU是最廉价的资源。 ### 高可伸缩的运行架构 经过以上这些分析。我们可以得到现在的运行架构,如下图: 31 | ![runtime architecture](img/pomelo-runtime-architecture.png) 运行架构说明: 32 | * 客户端通过websocket长连接连到connector服务器群。 * connector负责承载连接,并把请求转发到后端的服务器群。 * 后端的服务器群主要包括按场景分区的场景服务器(area)、聊天服务器(chat)和状态服务器等(status),这些服务器负责各自的业务逻辑。真实的案例中还会有各种其它类型的服务器。 * 后端服务器处理完逻辑后把结果返回给connector,再由connector广播回给客户端。 master负责统一管理这些服务器,包括各服务器的启动、监控和关闭等功能。 这个运行架构符合了刚才提到的几个伸缩性原则: 33 | * 前后端进程分离,把承载连接和广播的压力尽量分出去。 * 进程的粒度尽量小,把功能细分到各个服务器 * 按场景分区 前面提到4个游戏服务器框架,只有bigworld和pomelo符合这样的架构,当然bigworld实现的还要更复杂。 现在的问题是,这个运行架构是个分布式架构,而且并不简单,那就带来以下问题: 34 | * 需要多少的代码来实现这样的运行架构? * 服务器类型、数量管理和扩展有点复杂,该怎么管理? * 服务器之间会有一堆的相互rpc调用,实现起来怎么简化? * 分布式的开发和调试并不容易,消耗资源量过大,过于重量级,多进程bug定位困难,该怎么解决? Pomelo和node.js将很轻松地帮我们解决这些难题,我们下一节将讨论。 35 | ##node.js、pomelo与游戏服务器 Node.js的特点与游戏服务器极其符合。列举如下: 36 | * 对网络IO的处理能力,node.js生来就是为IO而生的,而游戏服务器刚好是网络密集型的应用。 * 单线程的应用模型,node.js的单线程处理能力远比其它语言强大,而单线程处理游戏逻辑是最简单,最不容易出错,而且还可能出现死锁、锁竞争的情况。 * 语言与轻量的开发模型。Javascript语言已经不是昔日的吴下阿蒙,它不仅由于脚本语言的轻量、简单带来了开发效率的提升。还可以与一些类型的客户端共享部分代码,如html5,unity3d的js客户端等。 * 语言的动态性带来了很多框架设计的便利,如设计DSL,实现Convention over configuration。尽管这方面比ruby稍差,但在pomelo框架中使用已经足够好了。 Pomelo是基于node.js搭建的游戏服务器框架,它在灵活性、扩展能力,轻量级调试方面具有无可比拟的优势。我们先简单回答第三章最末的几个问题: 37 | * 用pomelo来实现以上的运行架构几乎是零代码的,因为它在设计时天生就具备这样的架构。 * 服务器类型、数量的管理极简单,利用鸭子类型、目录定义,只要一个简单json配置文件就可以实现所有服务器的管理。 * rpc调用可以实现完全零配置,也不用生成stub。感谢js语言的动态性,基于Convention over configuration的原则,可以直接实现rpc调用。 * 分布式的开发和调试只占用很少的资源,启动极其迅速,十几个进程只用10秒不到的时间完全启动。多进程调试与单进程调试没有任何区别,只在一个console里搞定。 在本系列文章后面将会陆续讨论pomelo是怎么实现以上如此方便的特性, 以及这些设计带来的启发。 38 | ## 小结 本文分析了游戏服务器框架的市场现状,一个高可伸缩游戏服务器架构的设计原则及运行架构。Node.js与pomelo在解决高并发和分布式架构中起到的作用。下文我们将深入分析pomelo在解决复杂的游戏服务器运行架构中提供了哪些便利。 -------------------------------------------------------------------------------- /node-game-server-2/img/chatDir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/chatDir.png -------------------------------------------------------------------------------- /node-game-server-2/img/chatFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/chatFlow.png -------------------------------------------------------------------------------- /node-game-server-2/img/game-server-dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/game-server-dir.png -------------------------------------------------------------------------------- /node-game-server-2/img/multi-json-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/multi-json-architecture.png -------------------------------------------------------------------------------- /node-game-server-2/img/multi_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/multi_chat.png -------------------------------------------------------------------------------- /node-game-server-2/img/serverAbstraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/serverAbstraction.png -------------------------------------------------------------------------------- /node-game-server-2/img/single-json-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/single-json-architecture.png -------------------------------------------------------------------------------- /node-game-server-2/img/uberchat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-2/img/uberchat.png -------------------------------------------------------------------------------- /node-game-server-2/node-game-server-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 147 | 深入浅出node.js游戏服务器开发--分布式聊天服务器搭建 148 | 149 | 150 | 151 |

深入浅出node.js游戏服务器开发--分布式聊天服务器搭建

152 | 153 |

在上一篇文章中, 我们介绍了游戏服务器的基本架构,相关框架和node.js开发游戏服务器的优势。本文我们将通过聊天服务器的设计与开发,来更深入地理解pomelo开发应用的基本流程、开发思路与相关的概念。本文并不是开发聊天服务器的tutorial,如果需要tutorial和源码可以看文章最后的参考资料。

154 | 155 |

为什么是聊天服务器?

156 | 157 |

我们目标是搭建游戏服务器,为什么从聊天开始呢?

158 | 159 |

聊天可认为是简化的实时游戏,它与游戏服务器有着很多共通之处,如实时性、频道、广播等。由于游戏在场景管理、客户端动画等方面有一定的复杂性,并不适合作为pomelo的入门应用。聊天应用通常是node.js入门接触的第一个应用,因此更适合做入门教程。

160 | 161 |

Pomelo是游戏服务器框架,本质上也是高实时、可扩展、多进程的应用框架。除了在library中有一部分游戏专用的库,其余部分框架完全可用于开发高实时web应用。而且与现在有的node.js高实时应用框架如derby、socketstream、meteor等比起来有更好的可伸缩性。

162 | 163 |

对于大多数开发者而言,node.js的入门应用都是一个基于socket.io开发的普通聊天室, 由于它是基于单进程的node.js开发的, 在可扩展性上打了一定折扣。例如要扩展到类似irc那样的多频道聊天室, 频道数量的增多必然会导致单进程的node.js支撑不住。

164 | 165 |

而基于pomelo框架开发的聊天应用天生就是多进程的,可以非常容易地扩展服务器类型和数量。

166 | 167 |

从单进程到多进程,从socket.io到pomelo

168 | 169 |

一个基于socket.io的原生聊天室应用架构, 以[uberchat] (http://github.com/joshmarshall/uberchat )为例。

170 | 171 |

它的应用架构如下图所示:

172 | 173 |

uberchat

174 | 175 |

服务端由单个node.js进程组成的chat server来接收websocket请求。

176 | 177 |

它有以下缺点:

178 | 179 |
    180 |
  1. 可扩展性差:只支持单进程的node.js, 无法根据room/channel分区, 也无法将广播的压力与处理逻辑的压力分开。

  2. 181 |
  3. 代码量大:基于socket.io做了简单封装,服务端就写了约430行代码。

  4. 182 |
183 | 184 | 185 |

用pomelo来写这个框架可完全克服以上缺点,并且代码量只要区区100多行。

186 | 187 |

我们要搭建的pomelo聊天室具有如下的运行架构:

188 | 189 |

multi chat

190 | 191 |

在这个架构里, 前端服务器也就是connector专门负责承载连接, 后端的聊天服务器则是处理具体逻辑的地方。 192 | 这样扩展的运行架构具有如下优势: 193 | * 负载分离:这种架构将承载连接的逻辑与后端的业务处理逻辑完全分离,这样做是非常必要的, 尤其是广播密集型应用(例如游戏和聊天)。密集的广播与网络通讯会占掉大量的资源,经过分离后业务逻辑的处理能力就不再受广播的影响。

194 | 195 | 199 | 200 | 201 |

聊天服务器开发架构

202 | 203 |

game server与web server

204 | 205 |

聊天服务器项目中分生成了game-server目录、web-server目录与shared目录,如下图所示:

206 | 207 |

chat directory

208 | 209 |

这样也将应用天然地隔离成了两个,game server与web server。

210 | 211 | 216 | 217 | 218 |

服务器定义与应用目录

219 | 220 |

Game server才是游戏服务器的真正入口,游戏逻辑都在里, 我们简单看一下game-server的目录结构,如下图所示:

221 | 222 |

game directory

223 | 224 |

servers目录下所有子目录定义了各种类型的服务器,而每个servers目录下基本都包含了handler和remote两个目录。 225 | 这是pomelo的创新之处,用极简的配置实现游戏服务器的定义,后文会解释handler和remote。

226 | 227 |

通过pomelo,游戏开发者可以自由地定义自己的服务器类型,分配和管理进程资源。在pomelo中,根据服务器的职责不同,服务器主要分为前端服务器(frontend)和后端服务器(backend)两大类。其中,前端服务器负责承载客户端的连接和维护session信息,所有服务器与客户端的消息都会经过前端服务器;后端服务器负责接收前端服务器分发过来的请求,实现具体的游戏逻辑,并把消息回推给前端服务器,最后发送给客户端。如下图所示:

228 | 229 |

servers

230 | 231 |

动态语言的面向对象有个基本概念叫鸭子类型。在pomelo中,服务器的抽象也同样可以比喻为鸭子,服务器的对外接口只有两类, 一类是接收客户端的请求, 叫做handler, 一类是接收RPC请求, 叫做remote, handler和remote的行为决定了服务器长什么样子。 因此开发者只需要定义好handler和remote两类的行为, 就可以确定这个服务器的类型。 232 | 例如chat服务器目前的行为只有两类,分别是定义在handler目录中的chatHandler.js,和定义在remote目录中的chatRemote.js。只要定义好这两个类的方法,聊天服务器的对外接口就确定了。

233 | 234 |

搭建聊天服务器

235 | 236 |

准备知识

237 | 238 |

pomelo的客户端服务器通讯

239 | 240 |

pomelo的客户端和服务器之间的通讯可以分为三种:

241 | 242 | 264 | 265 | 266 |

以上是javascript的api, 其它客户端的API基本与这个类型。由于API与ajax极其类似,所有web应用的开发者对此都不陌生。

267 | 268 |

session介绍

269 | 270 |

与web服务器类似,session是游戏服务器存放用户会话的抽象。但与web不同,游戏服务器的session是基于长连接的, 一旦建立就一直保持。这反而比web中的session更直接,也更简单。 由于长连接的session不会web应用一样由于连接断开重连带来session复制之类的问题,简单地将session保存在前端服务器的内存中是明智的选择。

271 | 272 |

在pomelo中session也是key/value对象,其主要作用是维护当前用户信息,例如:用户的id,所连接的前端服务器id等。session由前端服务器维护,前端服务器在分发请求给后端服务器时,会复制session并连同请求一起发送。任何直接在session上的修改,只对本服务器进程生效,并不会影响到用户的全局状态信息。如需修改全局session里的状态信息,需要调用前端服务器提供的RPC服务。

273 | 274 |

channel与广播

275 | 276 |

广播在游戏中是极其重要的,几乎大部分的消息都需要通过广播推送到客户端,再由客户端播放接收的消息。而channel则是服务器端向客户端进行消息广播的通道。 277 | 可以把channel看成一个用户id的容器.把用户id加入到channel中成为当中的一个成员,之后向channel推送消息,则该channel中所有的成员都会收到消息。channel只适用于服务器进程本地,即在服务器进程A创建的channel和在服务器进程B创建的channel是两个不同的channel,相互不影响。

278 | 279 |

服务器之间RPC通讯

280 | 281 |

从之前的文章可以了解到,在pomelo中,游戏服务器其实是一个多进程相互协作的环境。各个进程之间通信,主要是通过底层统一的RPC框架来实现,服务器间的RPC调用也实现了零配置。具体RPC调用的代码如下:

282 | 283 |
```javascript
284 | app.rpc.chat.chatRemote.add(session, uid, app.get('serverId'), function(data){});
285 | ```
286 | 
287 | 288 |

其中app是pomelo的应用对象,app.rpc表明了是前后台服务器的Remote rpc调用,后面的参数分别代表服务器的名称、对应的文件名称及方法名。为了实现这个rpc调用,则只需要在对应的chat/remote/中新建文件chatRemote.js,并实现add方法。

289 | 290 |

聊天室流程概述

291 | 292 |

下图列出了聊天室进行聊天的完整流程:

293 | 294 |

chat flow

295 | 296 |

通过以上流程, 我们可以看到pomelo的基本请求流程和用法。本文不是聊天室的tutorial,因此下面列出的代码不是完整的,而是用极简的代码来说明pomelo的使用流程和api。

297 | 298 |

进入聊天室

299 | 300 |

客户端向前端服务器发起登录请求:

301 | 302 |
```javascript
303 |     pomelo.request('connector.entryHandler.enter', {user:userInfo}, function(){});
304 | ```
305 | 
306 | 307 |

用户进入聊天室后,服务器端首先需要完成用户的session注册同时绑定用户离开事件:

308 | 309 |
```javascript
310 | session.bind(uid);
311 | session.on('closed', onUserLeave.bind(null, app));
312 | ```
313 | 
314 | 315 |

另外,服务器端需要通过调用rpc方法将用户加入到相应的channel中;同时在rpc方法中,服务器端需要将该用户的上线消息广播给其他用户,最后服务器端向客户端返回当前channel中的用户列表信息。

316 | 317 |
```javascript
318 |     app.rpc.chat.chatRemote.add(session, uid, serverId, function(){});
319 | ```
320 | 
321 | 322 |

发起聊天

323 | 324 |

客户端向服务端发起聊天请求,请求消息包括聊天内容,发送者和发送目标信息。消息的接收者可以聊天室里所有的用户,也可以是某一特定用户。

325 | 326 |

服务器端根据客户端的发送的请求,进行不同形式的消息广播。如果发送目标是所有用户,服务器端首先会选择channel中的所有用户,然后向channel发送消息,最后前端服务器就会将消息分别发送给channel中取到的用户;如果发送目标只是某一特定用户,发送过程和之前完全一样,只是服务器端首先从channel中选择的只是一个用户,而不是所有用户。

327 | 328 |
```javascript
329 |     if(msg.target == '*') 
330 |         channel.pushMessage(param);
331 |     else
332 |         channelService.pushMessageByUids(param,[{uid:uid, sid:sid}]);
333 | ```
334 | 
335 | 336 |

接收聊天消息

337 | 338 |

客户端接收广播消息,并将消息并显示即可。

339 | 340 |
```javascript
341 |     pomelo.on('onChat', function() {
342 |         addMessage(data.from, data.target, data.msg);
343 |         $("#chatHistory").show();
344 |     });
345 | ```
346 | 
347 | 348 |

退出聊天室

349 | 350 |

用户在退出聊天室时,必须完成一些清理工作。在session断开连接时,通过rpc调用将用户从channel中移除。在用户退出前,还需要将自己下线的消息广播给所有其他用户。

351 | 352 |
```javascript
353 |     app.rpc.chat.chatRemote.kick(session, uid, serverId, channelName, null);
354 | ```
355 | 
356 | 357 |

聊天服务器的可伸缩性与扩展讨论

358 | 359 |

上一讲已经谈到pomelo在提供了一个高可伸缩性的运行架构,对于聊天服务器同样如此。如果想从单频道聊天室扩展到多频道聊天室,增加的代码几乎为零。大部分工作只是在进行服务器和路由的配置。对于服务器配置只需要修改json配置文件即可,而对于路由配置只需要增加一个路由算法即可。在pomelo中,开发者可以自己配置客户端到服务器的路由规则,这样会使得游戏开发更加灵活。

360 | 361 |

我们来看一下配置json文件对服务器运行架构的影响:

362 | 363 | 366 | 367 | 368 |

json 配置

369 | 370 | 373 | 374 | 375 |

json 配置

376 | 377 |

另外,在0.3版本的pomelo中增加了动态增删服务器的功能,开发者可以在不停服务的情况下根据当前应用运行的负载情况添加新的服务器或者停止闲置的服务器,这样可以让服务器资源得到更充分的利用。

378 | 379 |

总结

380 | 381 |

本文通过聊天服务器的搭建过程,分析了pomelo开发应用的基本流程,基本架构与相关概念。有了这些知识我们可以轻松地使用pomelo搭建高实时应用了。 在后文中我们将分析更复杂的游戏案例,并且会对架构中的一些实现深入剖析。

382 | 383 |

相关资源:

384 | 385 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /node-game-server-2/node-game-server-2.md: -------------------------------------------------------------------------------- 1 | #深入浅出node.js游戏服务器开发--分布式聊天服务器搭建 2 | 3 | 在上一篇文章中, 我们介绍了游戏服务器的基本架构,相关框架和node.js开发游戏服务器的优势。本文我们将通过聊天服务器的设计与开发,来更深入地理解pomelo开发应用的基本流程、开发思路与相关的概念。本文并不是开发聊天服务器的tutorial,如果需要tutorial和源码可以看文章最后的参考资料。 4 | 5 | 6 | ##为什么是聊天服务器? 7 | 8 | 9 | ###我们目标是搭建游戏服务器,为什么从聊天开始呢? 10 | 11 | 聊天可认为是简化的实时游戏,它与游戏服务器有着很多共通之处,如实时性、频道、广播等。由于游戏在场景管理、客户端动画等方面有一定的复杂性,并不适合作为pomelo的入门应用。聊天应用通常是node.js入门接触的第一个应用,因此更适合做入门教程。 12 | 13 | Pomelo是游戏服务器框架,本质上也是高实时、可扩展、多进程的应用框架。除了在library中有一部分游戏专用的库,其余部分框架完全可用于开发高实时web应用。而且与现在有的node.js高实时应用框架如derby、socketstream、meteor等比起来有更好的可伸缩性。 14 | 15 | 对于大多数开发者而言,node.js的入门应用都是一个基于socket.io开发的普通聊天室, 由于它是基于单进程的node.js开发的, 在可扩展性上打了一定折扣。例如要扩展到类似irc那样的多频道聊天室, 频道数量的增多必然会导致单进程的node.js支撑不住。 16 | 17 | 而基于pomelo框架开发的聊天应用天生就是多进程的,可以非常容易地扩展服务器类型和数量。 18 | 19 | ###从单进程到多进程,从socket.io到pomelo 20 | 一个基于socket.io的原生聊天室应用架构, 以[uberchat] (http://github.com/joshmarshall/uberchat )为例。 21 | 22 | 它的应用架构如下图所示: 23 | 24 | ![uberchat](img/uberchat.png) 25 | 26 | 27 | 服务端由单个node.js进程组成的chat server来接收websocket请求。 28 | 29 | 30 | 它有以下缺点: 31 | 32 | 1. 可扩展性差:只支持单进程的node.js, 无法根据room/channel分区, 也无法将广播的压力与处理逻辑的压力分开。 33 | 34 | 2. 代码量大:基于socket.io做了简单封装,服务端就写了约430行代码。 35 | 36 | 37 | 用pomelo来写这个框架可完全克服以上缺点,并且代码量只要区区100多行。 38 | 39 | 我们要搭建的pomelo聊天室具有如下的运行架构: 40 | 41 | ![multi chat](img/multi_chat.png) 42 | 43 | 在这个架构里, 前端服务器也就是connector专门负责承载连接, 后端的聊天服务器则是处理具体逻辑的地方。 44 | 这样扩展的运行架构具有如下优势: 45 | * 负载分离:这种架构将承载连接的逻辑与后端的业务处理逻辑完全分离,这样做是非常必要的, 尤其是广播密集型应用(例如游戏和聊天)。密集的广播与网络通讯会占掉大量的资源,经过分离后业务逻辑的处理能力就不再受广播的影响。 46 | 47 | * 切换简便:因为有了前、后端两层的架构,用户可以任意切换频道或房间都不需要重连前端的websocket。 48 | 49 | * 扩展性好:用户数的扩展可以通过增加connector进程的数量来支撑。频道的扩展可以通过哈希等算法负载均衡到多台聊天服务器上。理论上这个架构可以实现频道和用户的无限扩展。 50 | 51 | 52 | 53 | ##聊天服务器开发架构 54 | 55 | ###game server与web server 56 | 57 | 聊天服务器项目中分生成了game-server目录、web-server目录与shared目录,如下图所示: 58 | 59 | ![chat directory](img/chatDir.png) 60 | 61 | 这样也将应用天然地隔离成了两个,game server与web server。 62 | 63 | * Game server, 即游戏服务器,所有的游戏服务器逻辑都在里实现。客户端通过websocket(0.3版会支持tcp的socket)连到game server。game-server/app.js是游戏服务器的运行入口。 64 | * Web server,即web服务器, 也可以认为是游戏服务器的一个web客户端, 所有客户端的js代码,web端的html、css资源都存放在这里,web服务端的用户登录、认证等功能也在这里实现。pomelo也提供了其它客户端,包括ios、android、unity3D等。 65 | * Shared目录,假如客户端是web,由于服务端和客户端都是javascript写的,这时node.js的代码重用优势就体现出来了。shared目录下可以存放客户端、服务端共用的常量、算法。真正做到一遍代码, 前后端共用。 66 | 67 | 68 | 69 | ###服务器定义与应用目录 70 | Game server才是游戏服务器的真正入口,游戏逻辑都在里, 我们简单看一下game-server的目录结构,如下图所示: 71 | 72 | ![game directory](img/game-server-dir.png) 73 | 74 | 75 | servers目录下所有子目录定义了各种类型的服务器,而每个servers目录下基本都包含了handler和remote两个目录。 76 | 这是pomelo的创新之处,用极简的配置实现游戏服务器的定义,后文会解释handler和remote。 77 | 78 | 通过pomelo,游戏开发者可以自由地定义自己的服务器类型,分配和管理进程资源。在pomelo中,根据服务器的职责不同,服务器主要分为前端服务器(frontend)和后端服务器(backend)两大类。其中,前端服务器负责承载客户端的连接和维护session信息,所有服务器与客户端的消息都会经过前端服务器;后端服务器负责接收前端服务器分发过来的请求,实现具体的游戏逻辑,并把消息回推给前端服务器,最后发送给客户端。如下图所示: 79 | 80 | ![servers](img/serverAbstraction.png) 81 | 82 | 动态语言的面向对象有个基本概念叫鸭子类型。在pomelo中,服务器的抽象也同样可以比喻为鸭子,服务器的对外接口只有两类, 一类是接收客户端的请求, 叫做handler, 一类是接收RPC请求, 叫做remote, handler和remote的行为决定了服务器长什么样子。 因此开发者只需要定义好handler和remote两类的行为, 就可以确定这个服务器的类型。 83 | 例如chat服务器目前的行为只有两类,分别是定义在handler目录中的chatHandler.js,和定义在remote目录中的chatRemote.js。只要定义好这两个类的方法,聊天服务器的对外接口就确定了。 84 | 85 | 86 | 87 | ##搭建聊天服务器 88 | ###准备知识 89 | ####pomelo的客户端服务器通讯 90 | pomelo的客户端和服务器之间的通讯可以分为三种: 91 | 92 | * request-response 93 | 94 | pomelo中最常用的就是request-response模式,客户端发送请求,服务器异步响应。客户端的请求发送形式类似ajax类似: 95 | 96 | ``` 97 | pomelo.request(url, msg, function(data){}); 98 | ``` 99 | 100 | 第一个参数为请求地址,完整的请求地址主要包括三个部分:服务器类型、服务端相应的文件名及对应的方法名。第二个参数是消息体,消息体为json格式,第三个参数是回调函数,请求的响应将会把结果置入这个回调函数中返回给客户端。 101 | 102 | * notify 103 | 104 | notify与request—response类似,唯一区别是客户端只负责发送消息到服务器,客户端不接收服务器的消息响应。 105 | 106 | ``` 107 | pomelo.notify(url, msg); 108 | ``` 109 | 110 | * push 111 | 112 | push则是服务器主动向客户端进行消息推送,客户端根据路由信息进行消息区分,转发到后。通常游戏服务器都会发送大量的这类广播。 113 | 114 | ``` 115 | pomelo.on(route, function(data){}); 116 | ``` 117 | 118 | 以上是javascript的api, 其它客户端的API基本与这个类型。由于API与ajax极其类似,所有web应用的开发者对此都不陌生。 119 | 120 | ####session介绍 121 | 122 | 与web服务器类似,session是游戏服务器存放用户会话的抽象。但与web不同,游戏服务器的session是基于长连接的, 一旦建立就一直保持。这反而比web中的session更直接,也更简单。 由于长连接的session不会web应用一样由于连接断开重连带来session复制之类的问题,简单地将session保存在前端服务器的内存中是明智的选择。 123 | 124 | 在pomelo中session也是key/value对象,其主要作用是维护当前用户信息,例如:用户的id,所连接的前端服务器id等。session由前端服务器维护,前端服务器在分发请求给后端服务器时,会复制session并连同请求一起发送。任何直接在session上的修改,只对本服务器进程生效,并不会影响到用户的全局状态信息。如需修改全局session里的状态信息,需要调用前端服务器提供的RPC服务。 125 | 126 | 127 | ####channel与广播 128 | 广播在游戏中是极其重要的,几乎大部分的消息都需要通过广播推送到客户端,再由客户端播放接收的消息。而channel则是服务器端向客户端进行消息广播的通道。 129 | 可以把channel看成一个用户id的容器.把用户id加入到channel中成为当中的一个成员,之后向channel推送消息,则该channel中所有的成员都会收到消息。channel只适用于服务器进程本地,即在服务器进程A创建的channel和在服务器进程B创建的channel是两个不同的channel,相互不影响。 130 | 131 | ####服务器之间RPC通讯 132 | 从之前的文章可以了解到,在pomelo中,游戏服务器其实是一个多进程相互协作的环境。各个进程之间通信,主要是通过底层统一的RPC框架来实现,服务器间的RPC调用也实现了零配置。具体RPC调用的代码如下: 133 | 134 | ```javascript 135 | app.rpc.chat.chatRemote.add(session, uid, app.get('serverId'), function(data){}); 136 | ``` 137 | 138 | 其中app是pomelo的应用对象,app.rpc表明了是前后台服务器的Remote rpc调用,后面的参数分别代表服务器的名称、对应的文件名称及方法名。为了实现这个rpc调用,则只需要在对应的chat/remote/中新建文件chatRemote.js,并实现add方法。 139 | 140 | 141 | ###聊天室流程概述 142 | 143 | 下图列出了聊天室进行聊天的完整流程: 144 | 145 | ![chat flow](img/chatFlow.png) 146 | 147 | 148 | 通过以上流程, 我们可以看到pomelo的基本请求流程和用法。本文不是聊天室的tutorial,因此下面列出的代码不是完整的,而是用极简的代码来说明pomelo的使用流程和api。 149 | 150 | ###进入聊天室 151 | 152 | 客户端向前端服务器发起登录请求: 153 | 154 | ```javascript 155 | pomelo.request('connector.entryHandler.enter', {user:userInfo}, function(){}); 156 | ``` 157 | 158 | 用户进入聊天室后,服务器端首先需要完成用户的session注册同时绑定用户离开事件: 159 | 160 | ```javascript 161 | session.bind(uid); 162 | session.on('closed', onUserLeave.bind(null, app)); 163 | ``` 164 | 165 | 另外,服务器端需要通过调用rpc方法将用户加入到相应的channel中;同时在rpc方法中,服务器端需要将该用户的上线消息广播给其他用户,最后服务器端向客户端返回当前channel中的用户列表信息。 166 | 167 | ```javascript 168 | app.rpc.chat.chatRemote.add(session, uid, serverId, function(){}); 169 | ``` 170 | 171 | ###发起聊天 172 | 客户端向服务端发起聊天请求,请求消息包括聊天内容,发送者和发送目标信息。消息的接收者可以聊天室里所有的用户,也可以是某一特定用户。 173 | 174 | 服务器端根据客户端的发送的请求,进行不同形式的消息广播。如果发送目标是所有用户,服务器端首先会选择channel中的所有用户,然后向channel发送消息,最后前端服务器就会将消息分别发送给channel中取到的用户;如果发送目标只是某一特定用户,发送过程和之前完全一样,只是服务器端首先从channel中选择的只是一个用户,而不是所有用户。 175 | 176 | ```javascript 177 | if(msg.target == '*') 178 | channel.pushMessage(param); 179 | else 180 | channelService.pushMessageByUids(param,[{uid:uid, sid:sid}]); 181 | ``` 182 | 183 | ###接收聊天消息 184 | 客户端接收广播消息,并将消息并显示即可。 185 | 186 | ```javascript 187 | pomelo.on('onChat', function() { 188 | addMessage(data.from, data.target, data.msg); 189 | $("#chatHistory").show(); 190 | }); 191 | ``` 192 | 193 | ###退出聊天室 194 | 用户在退出聊天室时,必须完成一些清理工作。在session断开连接时,通过rpc调用将用户从channel中移除。在用户退出前,还需要将自己下线的消息广播给所有其他用户。 195 | 196 | ```javascript 197 | app.rpc.chat.chatRemote.kick(session, uid, serverId, channelName, null); 198 | ``` 199 | 200 | 201 | ##聊天服务器的可伸缩性与扩展讨论 202 | 上一讲已经谈到pomelo在提供了一个高可伸缩性的运行架构,对于聊天服务器同样如此。如果想从单频道聊天室扩展到多频道聊天室,增加的代码几乎为零。大部分工作只是在进行服务器和路由的配置。对于服务器配置只需要修改json配置文件即可,而对于路由配置只需要增加一个路由算法即可。在pomelo中,开发者可以自己配置客户端到服务器的路由规则,这样会使得游戏开发更加灵活。 203 | 204 | 我们来看一下配置json文件对服务器运行架构的影响: 205 | 206 | * 最简服务器与运行架构: 207 | 208 | ![json 配置](img/single-json-architecture.png) 209 | 210 | * 扩展后的服务器与运行架构: 211 | 212 | ![json 配置](img/multi-json-architecture.png) 213 | 214 | 215 | 另外,在0.3版本的pomelo中增加了动态增删服务器的功能,开发者可以在不停服务的情况下根据当前应用运行的负载情况添加新的服务器或者停止闲置的服务器,这样可以让服务器资源得到更充分的利用。 216 | 217 | 218 | ##总结 219 | 本文通过聊天服务器的搭建过程,分析了pomelo开发应用的基本流程,基本架构与相关概念。有了这些知识我们可以轻松地使用pomelo搭建高实时应用了。 在后文中我们将分析更复杂的游戏案例,并且会对架构中的一些实现深入剖析。 220 | 221 | 相关资源: 222 | 223 | * [pomelo构建聊天服务器tutorial](https://github.com/NetEase/pomelo/wiki/tutorial1--%E5%88%86%E5%B8%83%E5%BC%8F%E8%81%8A%E5%A4%A9) 224 | * [聊天服务器的源码](https://github.com/NetEase/chatofpomelo) 225 | * [线上聊天服务器demo](http://pomelo.netease.com/chat) 226 | 227 | -------------------------------------------------------------------------------- /node-game-server-3/Pomelo游戏开发-MarkdownPadPreview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pomelo游戏开发 5 | 6 | 274 | 275 | 276 | 277 |

基于Pomelo的MMO RPG开发

278 |

在这篇文章中,我们使用Pomelo框架来搭建一个简单的MMO RPG框架,我们的目标是做到以下几点: 279 | 在服务端搭建支持多个玩家的场景服务器。 280 | 支持多人同时在线,实时互动。 281 | 使用Pomelo框架进行服务端扩展。

282 |

开始之前

283 |

作为一个开源可扩展的游戏框架,pomelo为游戏开 284 | Pomelo为我们的游戏开发提供了什么: 285 | 提供可扩展的游戏服务器通信框架,包括客户端与服务器之间,服务器与服务器之间的通信。 286 | 可扩展的服务器架构,pomelo本身提供了良好的扩展性支持。 287 | 公共的库:如任务调度,AI控制,寻路等。 288 | 同时,Pomelo不做的: 289 | 服务端具体业务逻辑代码:Pomelo是一个游戏框架,不是一个具体的游戏,我们不会实现具体的业务逻辑代码的实现。 290 | 客户端开发:Pomelo 是一个服务端的框架,因此我们不关心客户端的逻辑代码。我们提供的客户端仅仅是一个长连接的客户端,服务消息的发送和接受,而对于客户端具体游戏逻辑的开发则不是我们关心的内容。

291 |

因此,在这篇文章中,我们主要关注下面几点: 292 | 基于Pomelo框架的MMO RPG游戏逻辑搭建,如何建立服务端的 293 | 客户端与服务端的通信:

294 |

场景服务器的搭建

295 |

场景服务器是游戏世界在服务端的抽象,是玩家所处的虚拟环境。一个基本的场景服务包括: 296 | 场景地图,玩家,道具等。。。

297 |

场景服务

298 |

严格来说,场景服务器的搭建并不属于Pomelo框架的内容,这里的场景服务器是一个,起到抛砖引玉的作用。 299 | 作为一个MMO RPG,肯定是要有供玩家 300 | 需要加入新的场景服务器, 301 | 1,加入新的服务器 302 | 2,

303 |

根据游戏类型和内容的不同,场景服务器的复杂程度也会千差万别,这里,我们以treasure为例,来构建出一个简单的游戏服务器场景。 304 | 首先,作为一个场景服务器,需要能够储存用户和宝物的信息,这里我们直接使用一个放在内存中的map来储存场景中的所有实体,同时,我们将所有场景中的实体都抽象为一个Entity对象,放在这个map中。 305 | 之后,场景服务器还需要有对外的接口。首先,是场景初始化的接口,我们在init方法中会设置场景信息,配置参数等。我们加入了AddEntity和RemoveEntity接口,来供外部请求来添加和删除实体。之后,除了添加和删除实体外,我们还提供了刷新场景中的宝物接口,这样,当满足条件时,外部事件会调用改接口来刷新地图中的宝物。

306 |

场景驱动

307 |

作为MMORPG中的场景服务,其中的内容是实时变化的,而这些数据的改变主要是通过两个方式:时间驱动和外部事件驱动。 308 | 在真实的世界中,时间是一个重要的驱动源,很多事物状态的改变都是通过时间来驱动的。而在游戏世界中,时间也是最重要的驱动因素之一。在游戏的场景服务器中,我们通过一个tick来驱动场景。

309 |

改定时器会定时调用场景的update接口,来更新场景中的内容。在我们的例子中,就是定时刷新场景中的宝物。

310 |

事件驱动就是外部的请求驱动,包括来自客户端的用户请求和来自服务端的

311 |

与客户端通信

312 |

我们使用pomelo中的connector服务器来建立客户端与与服务端之间的通信。 313 | 在Pomelo中,我们提供了三种通信方式,request/response模式,notify模式,push模式,下面

314 | 330 |

Pomelo客户端

331 |

pomelo客户端的作用主要是维护客户端与服务端之间的连接,将请求推送给客户端,并将服务端推送来的消息分发给对应的代码。 332 | 因此,pomelo客户端只是一个消息分发器,不包括实际的 333 | 现在,除了标准的JS客户端,还支持IOS,android,Java等。

334 |

基于pomelo的并行扩展

335 |

在上一节中,我们使用pomelo搭建了

336 |

连接服务器的扩展

337 |

场景服务器的扩展

338 |

更多的客户端

339 |

总结

340 |

在前两节中,我们使用Pomelo框架来构建了一个简单的MMO RPG服务,我们实现了一个独立的场景服务器和连接服务器,同客户端建立了连接并进行通信。之后,我们使用pomelo的分布式特性,对原有的架构进行扩展,构建出了一个分布式的MMORPG服务端框架。 341 | 但是,这只是一个简单的示例,MMORPG中的很多基础功能在这里都没有实现,如数据持久化,AI控制等,而这些功能在一个MMORPG中都是不可或缺的。在下面的文章中,我们请期待下一篇导读:深入游戏服务器场景管理-基于Node的游。 342 | 并提供了多种客户端的支持。 343 | 但是,这个仅仅

344 |

服务端场景管理: 345 | 场景的作用,复杂性。 346 | 设计思路:服务端场景。 347 | 客户端

348 |

一切以服务端为准

349 |

350 |

持久化

351 |

关于 352 | 简单来说就一条基本的要求:不要直接对所有数据进行初始化,就算是基于key-value数据库也不行。庞大的持久化数量和持久化带来的延时会压垮你的系统,而复杂的事务要求会让任何持久层的数据丢失都变成一场灾难。 353 | 与web服务相比,游戏服务器中的持久化有着不同的要求: 354 | 1,首先,在游戏服务中,需要持久化的数据量要远大于web服务。为了能够保存玩家的状态信息,玩家状态的改变最终都会写入数据库中。这些状态涵盖了各个方面,既包括一些重要的事件,如升级,拾取宝物,切换场景,也包括了一些日常状态,如移动导致的坐标改变,因为使用技能导致mp减少,由于被攻击而造成的hp下降等。与web服务相比,这些数据的产生频率和数量要大的多。因为在web服务中,玩家一般是需要主动的保存操作来触发持久化请求的,这就大大减少了持久化的压力。而在游戏服务中,需要持久化的数据是在玩家的日常行为中自动产生的,其频率和数量要远远大于web中的内容。

355 |

2,高实时要求。 356 | 对于大部分web服务来说,对实时性的要求并不高,由于持久化操作造成的短时间的等待是可以接受的,而由于玩家持久化的需求一般并不频繁,在持久化是造成的等待也不会影响到后面的请求。而在game服务中,游戏的响应时间在很大程度上影响了用户体验,因为直接持久化造成的长时间的延时是不可接受的。这种情况不但会影响用户体验,服务端也很有可能因为大量未处理的持久化请求而崩溃。

357 |

3,严格的事务要求。 358 | 在web服务中,由于玩家的操作大部分是一个简单的操作,因为对于持久化事务一般不会有严格的要求。而在game服务中,由于很多行为实际上是有严格的关系,如:移动后攻击,使用技能造成伤害,同时mp减少。。。这就要我们在处理

359 |

基于以上几点,简单的持久化操作无法满足我们的要求,无论是直接使用数据库还是通过一个基于内存的key-value数据库,在延时和事务性上都无法满足要求。

360 |

我们在Pomelo中使用了Data-sync模块,收集所有持久化请求,定时将这些请求写入后台服务器中。其结构如下:

361 |

Data-sync模块将游戏逻辑与持久化逻辑分离,在游戏完成加载之后,几乎所有的持久化操作都转为简单的内存数据读写,而持久化操作则用后台的Data-sync服务完成。

362 |

人工智能

363 |

无论是MMO

364 |

寻路

365 |

场景细分

366 | 367 | 368 | 369 | 370 | -------------------------------------------------------------------------------- /node-game-server-3/Pomelo游戏开发.md: -------------------------------------------------------------------------------- 1 | #深入浅出node.js游戏服务器开发3--基于Pomelo的MMO RPG开发 2 | 3 | 作者:张小刚 谢骋超 4 | 5 | 在上一篇文章中,我们介绍了如何使用Pomelo来搭建聊天服务器。在这篇文章中,我们为大家介绍如何使用Pomelo框架来搭建MMO RPG服务器,并分析其设计思路和实现方法。以此来帮助大家更好的理解和使用Pomelo框架,理解Pomelo框架游戏开发的基础流程,使用方法和设计理念。 6 | 7 | 本文中的游戏服务端架构,只是为了说明Pomelo的开发理念和设计思路,并不是基于Pomelo开发的唯一方案,开发者完全可以根据自己的实际应用环境设计不同的服务端架构。 8 | 9 | ##开始之前 10 | ###Pomelo框架与MMO RPG 11 | 12 | 13 | 我们曾在本系列[第一篇文章](http://www.infoq.com/cn/articles/game-server-development-1)介绍过pomelo的架构。我们先简单回顾一下Pomelo为我们的游戏开发提供了什么: 14 | 15 | 16 | - 可扩展的服务器架构。Pomelo中对服务器端进行了抽象,将服务器分为承载链连接的前端服务器和负责业务逻辑的后端服务器,并提供了便利,高效的分布式扩展支持,让使用者可以在少改甚至不改的前提下实现对服务端的扩展。 17 | - 完整的通信框架:Pomelo对客户端与服务器之间,服务器与服务器之间的通信进行了完整的封装。开发者只需要按照默认规则来填写服务代码就可以完成接口的发布和调用,而不用考虑内部实现。 18 | - 大量游戏开发基础库:除了基本的服务器框架之外,Pomelo还提供了很多游戏开发需要用到的基础库,如任务调度,AI控制,寻路等,而这些库还会随着Pomelo的发展进一步完善。 19 | - 基于Node.JS轻量级的开发环境,以及大量的模块。相对于传统的开发语言,Node.JS有着轻量,快捷的特性(0.3版中启动一个包括10几个服务器集成的服务端只需要不到4S)。而活跃的开源社区也提供了大量的第三方模块。 20 | 21 | 作为Pomelo游戏开发的入门导引,本文的重点将放在游戏基础架构的搭建上,因此本文将主要介绍下面三个方面的内容:游戏服务端的构建,与客户端的通讯,服务器的扩展。 22 | 23 | ###本文的参考示例 24 | 我们使用demo Treasures作为本文的参考示例,游戏的截图如下: 25 | 26 | ![treasures](img/treasures.png) 27 | 28 | 从上图可以看出,在treasures中,玩家会进入一张遍布宝物的地图中,通过拾取宝物来获得积分。所有玩家的积分在右上角会有一个排名。下面是这个demo的关键点: 29 | 30 | - 每个玩家的行动对其他玩家来看都是实时的。 31 | - 在获取宝物时积分会更新积分榜,这个更新对所有玩家实时可见。 32 | - 宝物会定时刷新。 33 | 34 | 相对于一般的的MMO RPG,这个demo显得十分简陋:没有持久化,没有战斗,没有AI。。。但是,其中实现了MMO RPG中最核心的亮点功能:一个可以容纳多个在线玩家的游戏场景,以及玩家之间的实时互动。那些功能的确实可以让系统的结构更加清晰明确,成为一个非常好的项目导引。 35 | 36 | ##搭建游戏服务端 37 | 由于游戏逻辑十分简单,我们后端采用一台单独的场景服务器来运行整个游戏逻辑,同时加入一台连接服务器来承载用户连接,系统的设计如下: 38 | 39 | ![treasures](img/archSimple.png) 40 | 41 | 下面,我们就按照这个设计来搭建游戏服务端。 42 | 43 | ###编写场景服务 44 | 游戏场景是玩家所处的虚拟环境,而场景服务器就是游戏场景在服务端的抽象,根据游戏类型和内容的不同,场景服务器的复杂程度也会千差万别。在我们的例子中,场景的构成十分简单:一张开放的游戏地图,地图中的玩家,以及定时刷新的宝物。 45 | 46 | 首先,作为一个场景服务器,需要能够储存用户和宝物的信息,这里我们直接使用一个放在内存中的map来储存场景中的所有实体,同时,我们将所有场景中的实体都抽象为一个Entity对象,放在这个map中。 47 | 48 | 为了能够操纵这些数据,还需要暴露出对外接口,我们使用的接口有下面三种: 49 | 50 | 51 | - 初始化的接口:我们在init方法中会设置场景信息,配置参数等,并启动场景中的时钟循环。 52 | - 实体访问接口:如AddEntity和RemoveEntity接口等,我们使用这些接口来访问和修改场景中的实体。 53 | - 刷新场景中的宝物:当满足条件时,外部事件会调用该接口来刷新地图中的宝物。 54 | 55 | 56 | 我们通过一个无限循环的tick来驱动场景服务,在每一个tick中会更新场景中所有实体的状态信息,我们最终设计如下: 57 | 58 | ![treasures](img/areaArch.png) 59 | 60 | ###搭建场景服务器 61 | 在完成场景服务的代码之后,我们还需要提供一个场景服务运行的平台,在Pomelo中,我们通过搭建一个场景服务器来实现。 62 | 63 | Pomelo中的服务器分为两类,负责承载用户连接的前端服务器和运行逻辑的后端服务器。作为负责核心逻辑的场景服务器,自然是属于后端服务器,因此,我们在/game-sever/config/server.json中加入以下配置: 64 | 65 | ``` 66 | "area": [ 67 | {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1} 68 | ] 69 | ``` 70 | 71 | 其中的“area”是我们为场景服务器类型所起的名称,其对应的内容就是场景服务器的列表,可以看出,现在我们只加入了单台场景服务器。与聊天服务器相比,场景服务器的配置并没有明显区别,只是多了一个areaId的属性。我们使用这一属性来标明这个场景服务器对应的场景id,我们的建议设计是**一个游戏服务器对应一个独立的游戏场景**,这样可以大大减少场景管理的开销,并提高单场景的负载能力。Pomelo中每一个服务器就是一个独立的进程,相对于一个场景服务的开销,单独的服务器造成的开销是可以接受的。当然,这只是建议设计,框架本身完全支持一个游戏服务器对应多个游戏场景的设计。开发者可以根据具体的应用情况调整场景服务器的配置,通过加入场景管理逻辑,实现一个场景服务器和场景之间的自由配置。 72 | 73 | ###启动场景服务 74 | 在加入场景服务器之后,我们还需要对服务端的运行环境进行配置,在场景服务器启动时运行对应的场景服务。为了实现这一目标,我们需要在app.js中加入如下配置: 75 | 76 | ``` 77 | app.configure('production|development', 'area', function(){ 78 | var areaId = app.get('curServer').areaId; 79 | area.init(dataApi.area.findById(areaId)); 80 | }); 81 | ``` 82 | 83 | app.configure方法用来对指定的服务器进行配置,包括三个参数,前两个参数分别是运行环境和服务器类型的filter,第三个参数是在满足前面两个filter的情况下需要运行的代码。在上面的配置中,第一个参数"production|development"表示是针对线上和开发两种环境,之后的‘area’参数表示的是针对area服务器类型。 84 | 关于app.js初始化的更多内容,见[Pomelo启动流程](https://github.com/NetEase/pomelo/wiki/pomelo%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B)。 85 | 86 | ##与客户端通讯 87 | ###建立连接服务器 88 | 要与客户端通信,我们需要建立一个前端服务器,用来维护与客户端之间的连接。server.json中的配置如下: 89 | 90 | ``` 91 | "connector": [ 92 | {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true} 93 | ] 94 | ``` 95 | 其中的标志位“frontend:true”表示这是一个前端服务器,“clientPort:3010”则表示该服务器对外暴露前端。对于前端服务器,在启动时就会默认加载连接组件,因此我们不需要在app.js中进行额外的配置。在pomelo 0.3中,如果需要使用原生websocket等非默认的连接方式,则需要在app.js中加入相应配置。在客户端,我们在启动时连接对应的接口,就可以建立起与服务端的连接。 96 | 97 | ###处理客户端请求 98 | Pomelo中,我们提供了两种客户端向服务端发送请求的方法,request/response模式和notify模式。 99 | 100 | request/response 模式与web中的请求模式相似,是标准的请求/响应模式,对于客户端的一个请求,服务端会给出一个响应。以玩家的移动为例,当需要移动时,会发送一个请求给服务端,服务端会验证客户端的请求,并返回结果客户端,下图是具体请求流程: 101 | 102 | ![treasures](img/reqResp.png) 103 | 104 | 最上面的是客户端代码,在这里,我们向服务端发送一个移动请求。 105 | 之后,服务端会根据请求的route(area.playerHandler.move)找到对应的处理方法(/game-server/area/handler/playerHander.move),然后调用该方法来处理客户端的请求。当处理完成之后,会并在next方法中传入处理结果,结果会发回客户端,并作为回调函数的参数传回。 106 | 107 | notify模式的运行流程与request/response类似,只是当服务端处理请求后不会发送任何响应。客户端通过pomelo.notify方法来发出notify请求,notify请求的参数与pomelo.request相似,只是不需回调函数,notify方法的实例如下: 108 | 109 | ``` 110 | pomelo.notify('area.playerHandler.pick', params); 111 | ``` 112 | 113 | ###服务端消息推送 114 | 与web服务不同,game服务端会有大量的推送消息,要实现这一功能,我们使用pomelo中的push模式来实现。下图以“move”为例,展示了服务端的推送流程: 115 | 116 | ![treasures](img/pushMsg.png) 117 | 118 | Treasures中的广播功能是通过一个全局的channel来实现的,在游戏中的所有玩家在加入游戏后都会加入一个全局的channel中。当需要广播消失时,服务端就会调用这个全局的channel来对所有用户进行消息推送。 119 | 120 | ##扩展游戏服务端 121 | 在前面两节中,我们使用pomelo搭建了一个单节点的游戏服务器。在这一节中,将介绍如何使用Pomelo来对服务端进行扩展,搭建分布式的游戏服务。 122 | 123 | 由于游戏服务器的复杂性,像web服务器简单的水平扩展是不现实的,而根据业务逻辑的不同,不同的服务器也有着不同的扩展方案。因此我们分别以前面介绍的连接服务器和场景服务器为例,来对他们进行扩展。 124 | 125 | ###连接服务器的扩展 126 | 连接服务器作用是负责维护所有客户端的连接,负责客户端消息的接受和推送,在MMO RPG中,连接服务器往往是性能的热点之一,因此对连接服务器的扩展对于提高系统负载有很重要的现实意义。 127 | 在例子treasures中,我们通过加入一个负载均衡服务器(gate服务器),来实现连接服务器的扩展:当客户端登录时,会首先连接gate服务器,来分配一个连接服务器,之后,客户端会断开与gate服务器的连接,在与其对应的连接服务器建立连接,如下图所示: 128 | ![连接服务器扩展](img/mutiConnectors.png) 129 | 130 | 要实现这一功能,在服务端,我们首先要在server.config中加入新的前端服务器,代码如下: 131 | 132 | ``` 133 | "gate": [ 134 | {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true} 135 | ] 136 | ``` 137 | 138 | 之后,在gate服务器上编写对应的负载均衡接口,在例子中,我们采用了使用uid的crc值对服务器数目取模的方式,代码如下: 139 | 140 | ``` 141 | module.exports.dispatch = function(uid, connectors) { 142 | var index = Math.abs(crc.crc32(uid)) % connectors.length; 143 | return connectors[index]; 144 | }; 145 | ``` 146 | 147 | 最后,在客户端加入对应的连接代码,就完成了对连接服务器的扩展。 148 | 在我们的例子中只用到了两台连接服务器,而在实际应用中,可以根据实际环境,编写自己的负载均衡算法,加入更多的连接服务器。 149 | 150 | ###场景服务器的扩展 151 | 152 | 与连接服务器不同,场景服务器中包含着大量的状态信息,如果对单一的场景进行扩展,需要复杂的同步机制和大量远程调用。因此,在Pomelo中,我们的场景扩展是通过加入新的场景来进行的。 153 | 在设计游戏时,整个世界就被分为多个不同的场景。而在服务器端,一个场景则与一个独立的场景服务器相对应,场景服务的扩展可以通过加入新的场景来完成。下面,就以treasure为例,介绍一下场景服务的的扩展方法: 154 | 因为场景扩展是通过加入新的场景来完成的,所以首先要在server.json中加入新的场景服务器: 155 | 156 | 在加入场景服务器之后,我们还要保证发往场景服务器的请求可以被分发到正确的场景去。而Pomelo中,对于同一类型的服务器,默认的分法方法是采用随机分配的方式,这是不能满足我们要求的。因此,我们需要加入自己的路由算法,流程如下: 157 | 158 | ![自定义路由算法](img/areaRoute.png) 159 | 160 | 首先,我们编写了新的路由算法:根据用户所属的场景id进行投递。之后,我们在app.js中使用app.route方法来将我们的路由算法配置为针对area服务器的默认方法。app.route方法接受两个参数,第一个是需要自定义route的服务器类型,第二个参数就是具体的路由算法。我们分别将‘area’服务类型和我们编写的算法传入,就可以实现自定义的路由功能了。 161 | 162 | ###跨平台客户端 163 | 除了原有的JS客户端之外,pomelo还提供了多种其他的客户端,而不同的客户端可能会对应着不同的长连接协议。但是经过pomelo封装之后,在Pomelo服务端,不同客户端在连接上连接上是完全对等的。因此,你可以使用pomelo框架实现跨平台联机对战的(前提是你开发了针对多个平台的游戏客户端)。 164 | 165 | ###最终架构 166 | 经过扩展后,我们的最终服务器架构如图所示: 167 | 168 | ![muti architecture](img/archMuti.png) 169 | 170 | 171 | ##总结 172 | 在本文中,我们介绍了如何使用Pomelo框架来构建了一个包括连接服务器和场景服务器的MMO RPG服务端。并介绍了如何使用pomelo特性,来对游戏服务端进行扩展,并构建出了一个分布式的MMO RPG服务。 173 | 但是,这只是游戏服务端开发中最为基础的部分,MMO RPG中很多基础功能在这里都没有实现,如数据持久化,AI控制,寻路系统等。在下面的文章中,我们会进一步介绍Pomelo在游戏开发中的应用。 174 | 175 | ##参考示例 176 | [捡宝demo](https://github.com/NetEase/treasures "捡宝demo") 177 | 178 | [Pomelo启动流程](https://github.com/NetEase/pomelo/wiki/pomelo%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B) 179 | 180 | [Pomelo框架](https://github.com/NetEase/pomelo) 181 | -------------------------------------------------------------------------------- /node-game-server-3/img/archMuti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/archMuti.png -------------------------------------------------------------------------------- /node-game-server-3/img/archSimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/archSimple.png -------------------------------------------------------------------------------- /node-game-server-3/img/areaArch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/areaArch.png -------------------------------------------------------------------------------- /node-game-server-3/img/areaRoute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/areaRoute.png -------------------------------------------------------------------------------- /node-game-server-3/img/mutiConnectors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/mutiConnectors.png -------------------------------------------------------------------------------- /node-game-server-3/img/pushMsg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/pushMsg.png -------------------------------------------------------------------------------- /node-game-server-3/img/reqResp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/reqResp.png -------------------------------------------------------------------------------- /node-game-server-3/img/treasures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-3/img/treasures.png -------------------------------------------------------------------------------- /node-game-server-3/pomelo游戏开发.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 147 | 深入浅出node.js游戏服务器开发3--基于Pomelo的MMO RPG开发 148 | 149 | 150 | 151 |

深入浅出node.js游戏服务器开发3--基于Pomelo的MMO RPG开发

152 | 153 |

作者:张小刚 谢骋超

154 | 155 |

在上一篇文章中,我们介绍了如何使用Pomelo来搭建聊天服务器。在这篇文章中,我们为大家介绍如何使用Pomelo框架来搭建MMO RPG服务器,并分析其设计思路和实现方法。以此来帮助大家更好的理解和使用Pomelo框架,理解Pomelo框架游戏开发的基础流程,使用方法和设计理念。

156 | 157 |

本文中的游戏服务端架构,只是为了说明Pomelo的开发理念和设计思路,并不是基于Pomelo开发的唯一方案,开发者完全可以根据自己的实际应用环境设计不同的服务端架构。

158 | 159 |

开始之前

160 | 161 |

Pomelo框架与MMO RPG

162 | 163 |

我们曾在本系列第一篇文章曾介绍过pomelo的架构。我们先简单回顾一下Pomelo为我们的游戏开发提供了什么:

164 | 165 | 171 | 172 | 173 |

作为Pomelo游戏开发的入门导引,本文的重点将放在游戏基础架构的搭建上,因此本文将主要介绍下面三个方面的内容:游戏服务端的构建,与客户端的通讯,服务器的扩展。

174 | 175 |

本文的参考示例

176 | 177 |

我们使用demo Treasures作为本文的参考示例,游戏的截图如下:

178 | 179 |

treasures

180 | 181 |

从上图可以看出,在treasures中,玩家会进入一张遍布宝物的地图中,通过拾取宝物来获得积分。所有玩家的积分在右上角会有一个排名。下面是这个demo的关键点:

182 | 183 | 188 | 189 | 190 |

相对于一般的的MMO RPG,这个demo显得十分简陋:没有持久化,没有战斗,没有AI。。。但是,其中实现了MMO RPG中最核心的亮点功能:一个可以容纳多个在线玩家的游戏场景,以及玩家之间的实时互动。那些功能的确实可以让系统的结构更加清晰明确,成为一个非常好的项目导引。

191 | 192 |

搭建游戏服务端

193 | 194 |

由于游戏逻辑十分简单,我们后端采用一台单独的场景服务器来运行整个游戏逻辑,同时加入一台连接服务器来承载用户连接,系统的设计如下:

195 | 196 |

treasures

197 | 198 |

下面,我们就按照这个设计来搭建游戏服务端。

199 | 200 |

编写场景服务

201 | 202 |

游戏场景是玩家所处的虚拟环境,而场景服务器就是游戏场景在服务端的抽象,根据游戏类型和内容的不同,场景服务器的复杂程度也会千差万别。在我们的例子中,场景的构成十分简单:一张开放的游戏地图,地图中的玩家,以及定时刷新的宝物。

203 | 204 |

首先,作为一个场景服务器,需要能够储存用户和宝物的信息,这里我们直接使用一个放在内存中的map来储存场景中的所有实体,同时,我们将所有场景中的实体都抽象为一个Entity对象,放在这个map中。

205 | 206 |

为了能够操纵这些数据,还需要暴露出对外接口,我们使用的接口有下面三种:

207 | 208 | 213 | 214 | 215 |

我们通过一个无限循环的tick来驱动场景服务,在每一个tick中会更新场景中所有实体的状态信息,我们最终设计如下:

216 | 217 |

treasures

218 | 219 |

搭建场景服务器

220 | 221 |

在完成场景服务的代码之后,我们还需要提供一个场景服务运行的平台,在Pomelo中,我们通过搭建一个场景服务器来实现。

222 | 223 |

Pomelo中的服务器分为两类,负责承载用户连接的前端服务器和运行逻辑的后端服务器。作为负责核心逻辑的场景服务器,自然是属于后端服务器,因此,我们在/game-sever/config/server.json中加入以下配置:

224 | 225 |

226 |     "area": [
227 | 
228 |       {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1}
229 | 
230 |     ]
231 | 
232 | 233 |

其中的“area”是我们为场景服务器类型所起的名称,其对应的内容就是场景服务器的列表,可以看出,现在我们只加入了单台场景服务器。与聊天服务器相比,场景服务器的配置并没有明显区别,只是多了一个areaId的属性。我们使用这一属性来标明这个场景服务器对应的场景id,我们的建议设计是一个游戏服务器对应一个独立的游戏场景,这样可以大大减少场景管理的开销,并提高单场景的负载能力。Pomelo中每一个服务器就是一个独立的进程,相对于一个场景服务的开销,单独的服务器造成的开销是可以接受的。当然,这只是建议设计,框架本身完全支持一个游戏服务器对应多个游戏场景的设计。开发者可以根据具体的应用情况调整场景服务器的配置,通过加入场景管理逻辑,实现一个场景服务器和场景之间的自由配置。

234 | 235 |

启动场景服务

236 | 237 |

在加入场景服务器之后,我们还需要对服务端的运行环境进行配置,在场景服务器启动时运行对应的场景服务。为了实现这一目标,我们需要在app.js中加入如下配置:

238 | 239 |

240 |     app.configure('production|development', 'area', function(){
241 | 
242 |       var areaId = app.get('curServer').areaId;
243 |       area.init(dataApi.area.findById(areaId));
244 | 
245 |     });
246 | 
247 | 248 |

app.configure方法用来对指定的服务器进行配置,包括三个参数,前两个参数分别是运行环境和服务器类型的filter,第三个参数是在满足前面两个filter的情况下需要运行的代码。在上面的配置中,第一个参数"production|development"表示是针对线上和开发两种环境,之后的‘area’参数表示的是针对area服务器类型。

249 | 250 |

关于app.js初始化的更多内容,见Pomelo启动流程

251 | 252 |

与客户端通讯

253 | 254 |

建立连接服务器

255 | 256 |

要与客户端通信,我们需要建立一个前端服务器,用来维护与客户端之间的连接。server.json中的配置如下:

257 | 258 |

259 |     "connector": [
260 | 
261 |       {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true}
262 | 
263 |     ]
264 | 
265 | 266 |

其中的标志位“frontend:true”表示这是一个前端服务器,“clientPort:3010”则表示该服务器对外暴露前端。对于前端服务器,在启动时就会默认加载连接组件,因此我们不需要在app.js中进行额外的配置。在pomelo 0.3中,如果需要使用原生websocket等非默认的连接方式,则需要在app.js中加入相应配置。在客户端,我们在启动时连接对应的接口,就可以建立起与服务端的连接。

267 | 268 |

处理客户端请求

269 | 270 |

Pomelo中,我们提供了两种客户端向服务端发送请求的方法,request/response模式和notify模式。

271 | 272 |

request/response 模式与web中的请求模式相似,是标准的请求/响应模式,对于客户端的一个请求,服务端会给出一个响应。以玩家的移动为例,当需要移动时,会发送一个请求给服务端,服务端会验证客户端的请求,并返回结果客户端,下图是具体请求流程:

273 | 274 |

treasures

275 | 276 |

最上面的是客户端代码,在这里,我们向服务端发送一个移动请求。

277 | 278 |

之后,服务端会根据请求的route(area.playerHandler.move)找到对应的处理方法(/game-server/area/handler/playerHander.move),然后调用该方法来处理客户端的请求。当处理完成之后,会并在next方法中传入处理结果,结果会发回客户端,并作为回调函数的参数传回。

279 | 280 |

notify模式的运行流程与request/response类似,只是当服务端处理请求后不会发送任何响应。客户端通过pomelo.notify方法来发出notify请求,notify请求的参数与pomelo.request相似,只是不需回调函数,notify方法的实例如下:

281 | 282 |
```
283 | 
284 | pomelo.notify('area.playerHandler.pick', params);
285 | 
286 | ```
287 | 
288 | 289 |

服务端消息推送

290 | 291 |

与web服务不同,game服务端会有大量的推送消息,要实现这一功能,我们使用pomelo中的push模式来实现。下图以“move”为例,展示了服务端的推送流程:

292 | 293 |

treasures

294 | 295 |

Treasures中的广播功能是通过一个全局的channel来实现的,在游戏中的所有玩家在加入游戏后都会加入一个全局的channel中。当需要广播消失时,服务端就会调用这个全局的channel来对所有用户进行消息推送。

296 | 297 |

扩展游戏服务端

298 | 299 |

在前面两节中,我们使用pomelo搭建了一个单节点的游戏服务器。在这一节中,将介绍如何使用Pomelo来对服务端进行扩展,搭建分布式的游戏服务。

300 | 301 |

由于游戏服务器的复杂性,像web服务器简单的水平扩展是不现实的,而根据业务逻辑的不同,不同的服务器也有着不同的扩展方案。因此我们分别以前面介绍的连接服务器和场景服务器为例,来对他们进行扩展。

302 | 303 |

连接服务器的扩展

304 | 305 |

连接服务器作用是负责维护所有客户端的连接,负责客户端消息的接受和推送,在MMO RPG中,连接服务器往往是性能的热点之一,因此对连接服务器的扩展对于提高系统负载有很重要的现实意义。

306 | 307 |

在例子treasures中,我们通过加入一个负载均衡服务器(gate服务器),来实现连接服务器的扩展:当客户端登录时,会首先连接gate服务器,来分配一个连接服务器,之后,客户端会断开与gate服务器的连接,在与其对应的连接服务器建立连接,如下图所示:

308 | 309 |

连接服务器扩展

310 | 311 |

要实现这一功能,在服务端,我们首先要在server.config中加入新的前端服务器,代码如下:

312 | 313 |
```
314 | 
315 | "gate": [
316 | 
317 |   {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
318 | 
319 | ]
320 | 
321 | ```
322 | 
323 | 324 |

之后,在gate服务器上编写对应的负载均衡接口,在例子中,我们采用了使用uid的crc值对服务器数目取模的方式,代码如下:

325 | 326 |
```
327 | 
328 | module.exports.dispatch = function(uid, connectors) {
329 | 
330 |     var index = Math.abs(crc.crc32(uid)) % connectors.length;
331 | 
332 |     return connectors[index];
333 | 
334 | };
335 | 
336 | ```
337 | 
338 | 339 |

最后,在客户端加入对应的连接代码,就完成了对连接服务器的扩展。

340 | 341 |

在我们的例子中只用到了两台连接服务器,而在实际应用中,可以根据实际环境,编写自己的负载均衡算法,加入更多的连接服务器。

342 | 343 |

场景服务器的扩展

344 | 345 |

与连接服务器不同,场景服务器中包含着大量的状态信息,如果对单一的场景进行扩展,需要复杂的同步机制和大量远程调用。因此,在Pomelo中,我们的场景扩展是通过加入新的场景来进行的。

346 | 347 |

在设计游戏时,整个世界就被分为多个不同的场景。而在服务器端,一个场景则与一个独立的场景服务器相对应,场景服务的扩展可以通过加入新的场景来完成。下面,就以treasure为例,介绍一下场景服务的的扩展方法:

348 | 349 |

因为场景扩展是通过加入新的场景来完成的,所以首先要在server.json中加入新的场景服务器:

350 | 351 |

在加入场景服务器之后,我们还要保证发往场景服务器的请求可以被分发到正确的场景去。而Pomelo中,对于同一类型的服务器,默认的分法方法是采用随机分配的方式,这是不能满足我们要求的。因此,我们需要加入自己的路由算法,流程如下:

352 | 353 |

自定义路由算法

354 | 355 |

首先,我们编写了新的路由算法:根据用户所属的场景id进行投递。之后,我们在app.js中使用app.route方法来将我们的路由算法配置为针对area服务器的默认方法。app.route方法接受两个参数,第一个是需要自定义route的服务器类型,第二个参数就是具体的路由算法。我们分别将‘area’服务类型和我们编写的算法传入,就可以实现自定义的路由功能了。

356 | 357 |

跨平台客户端

358 | 359 |

除了原有的JS客户端之外,pomelo还提供了多种其他的客户端,而不同的客户端可能会对应着不同的长连接协议。但是经过pomelo封装之后,在Pomelo服务端,不同客户端在连接上连接上是完全对等的。因此,你可以使用pomelo框架实现跨平台联机对战的(前提是你开发了针对多个平台的游戏客户端)。

360 | 361 |

最终架构

362 | 363 |

经过扩展后,我们的最终服务器架构如图所示:

364 | 365 |

muti architecture

366 | 367 |

总结

368 | 369 |

在本文中,我们介绍了如何使用Pomelo框架来构建了一个包括连接服务器和场景服务器的MMO RPG服务端。并介绍了如何使用pomelo特性,来对游戏服务端进行扩展,并构建出了一个分布式的MMO RPG服务。

370 | 371 |

但是,这只是游戏服务端开发中最为基础的部分,MMO RPG中很多基础功能在这里都没有实现,如数据持久化,AI控制,寻路系统等。在下面的文章中,我们会进一步介绍Pomelo在游戏开发中的应用。

372 | 373 |

参考示例

374 | 375 |

捡宝demo

376 | 377 |

Pomelo启动流程

378 | 379 |

Pomelo框架

380 | 381 | -------------------------------------------------------------------------------- /node-game-server-4/img/TowerAOI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-4/img/TowerAOI.png -------------------------------------------------------------------------------- /node-game-server-4/img/area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-4/img/area.png -------------------------------------------------------------------------------- /node-game-server-4/img/pomelo-sync-use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-4/img/pomelo-sync-use.png -------------------------------------------------------------------------------- /node-game-server-4/img/pomelo-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node-game-server-4/img/pomelo-sync.png -------------------------------------------------------------------------------- /node-game-server-4/node-game-server-4.md: -------------------------------------------------------------------------------- 1 | #场景服务分析 2 | 在上篇文章中,我们使用Pomelo框架搭建一个分布式的游戏服务器,并介绍了游戏服务中最基本的两项服务,场景服务和连接服务。在本文中,我们对场景服务进行进一步的剖析,为大家讲解游戏服务器的主要内容,以及Pomelo框架所提供的相关服务。 3 | 4 | ##游戏场景分析 5 | 在网络游戏中,玩家会在一个共同的世界中进行互动。根据具体游戏类型的不同,这个世界可能会千差万别,可以是qq游戏中的一个房间,或者是一张游戏地图,或者是3D游戏中的一个固定的区域。 6 | 7 | 由于一个游戏世界往往十分庞大,为了负载,扩展等原因,我们往往会把整个游戏世界划分为多个不同的区域进行管理,其中的每个区域就被称为一个场景。整个游戏世界就是有多个场景组成,这些场景之间主要会有两种不同的组合方式: 8 | 9 | - 无缝组合:所有的游戏场景之间没有明显的界限,玩家可以在不同场景之间自由穿梭而不会有任何延时和停顿,就像在同一个巨大的场景中一样。在这种情况下,场景直接的整合和交互全部是由后台完成。实现这种架构需要负载的负载均衡机制和大量的RPC调用,在实现上较为复杂,对硬件负载也比较大。优点是方便灵活,可以方便的对某个场景的承载能力进行扩展,理论上可以做到‘无限大’的游戏场景。想商用的游戏框架bigworld就是使用这样的设计。 10 | 11 | - 独立场景:整个游戏世界会分为不同的区域(一般是不同的游戏地图),不同区域之间的玩家之间是不可见的,无法进行直接的互动。这种做法的好处是架构简单,不需要远程调用和分布式事务处理。缺点是单场景承载能力有限,场景大小和承载人数取决于节点的运算能力。而负载的提高主要通过增加新的游戏场景来实现。 12 | 13 | 在Pomelo中,我们采用了第二种场景划分方式,不同的场景之间是相互分离的,不同场景的玩家之间一般不会进行直接交流。 14 | 15 | 在上一篇文章中我们实现了场景服务器,并为客户端提供了服务。但是,这个场景服务器是非常简单的,缺少很多基本功能。一个真正的场景服务,除了基本的tick和数据管理之外,还包括很多其他服务,下面,让我们以lordofpomelo为例,看一下场景服务中的基本内容: 16 | 17 | ![area](./img/area.png) 18 | 19 | 从上图中可以看出,除了基本的实体管理功能外,场景服务还包括多种其他服务: 20 | 21 | - 数据管理:负责维护场景中的所有数据,包括地图数据,玩家数据,NPC,怪物等。数据的来源可以是数据库或者配置文件。 22 | - 消息服务:负责处理场景中的消息,并负责客户端的消息推送。比如用户请求的响应,发送广播消息等。 23 | - AI模块: 负责场景中所有自动化事件,比如怪物行为的驱动,玩家自动化行为的控制等。 24 | - 寻路服务:主要是负责地图中路径查询功能,即:给出地图中任意两点,根据地图信息计算出这两点之间的最短路径。 25 | 26 | 在本文中,我们将从之前介绍过的实体管理服务和数据服务出发,实现基于Pomelo-sync的数据持久化服务和基于Pomelo-AOI的广播服务。 27 | 28 | ##数据管理和持久化 29 | 在游戏场景中,有各种各样的数据:游戏地图数据,玩家数据,怪物数据,npc数据等。有的数据是静态不变的,如地图信息,怪物的配置,npc的位置等。而更多的数据则会随着用户操作(移动,攻击)和时间流逝而变化,而这些数据往往对玩家来说是非常重要的,因此就需要进行数据持久化功能。 30 | 31 | 如果对于这些变化的数据直接进行持久化,一方面,会对持久层带来很大的压力,另外一方面,由于数据读写都存在延时,因此可能会对之后的操作产生影响,因此,我们采用了异步持久化的方式来结果这一问题,在Pomelo框架中,我们使用Pomelo-sync模块来实现这一功能。 32 | ###Pomleo-sync 33 | Pomelo-sync是一个异步的数据同步模块,它会将对于所有的持久化操作加入一个队列中,并且定时轮询这一队列,执行持久化命令。,下图是Pomleo-sync模块的运行架构: 34 | 35 | ![area](./img/pomelo-sync.png) 36 | 37 | Pomelo-sync支持多种持久化对象,包括文件,网络IO等。同时,由于使用与Pomelo-sync模块的命令都会是写操作,因此可以进行写入数据的合并,对于同一id的同类型操作,pomelo-sync会自动的用后续命令覆盖之前的操作,从而减少了数据写入次数。 38 | 39 | ###使用Pomelo-sync 40 | 41 | 在Pomelo中,Pomelo-sync 模块被封装为sync组件。要使用Pomelo-sync的功能,只需在app.js中加载sync组件就可以了,配置如下: 42 | 43 | ``` 44 | app.configure('production|development', 'area|auth|connector|master', function() { 45 | var dbclient = require('./app/dao/mysql/mysql').init(app); 46 | app.load(pomelo.sync, {path:__dirname + '/app/dao/mapping', dbclient: dbclient}); 47 | }); 48 | 49 | ``` 50 | 初始化pomelo-sync需要两个参数,一个是sync指令的目录地址,一个是数据库连接。在加入配置之后,pomelo启动时就会加载pomelo-sync组件。 51 | 52 | 要使用pomelo-sync模块,需要调用sync组件的exec()接口,传入相应的参数。sync模块就会自动把对应的命令加入队列中,并定时执行。Pomelo-sync模块的使用方法如下图所示: 53 | 54 | ![pomelo-sync](./img/pomelo-sync-use.png) 55 | 56 | 可以看到,exec接口包括三个参数,分别是需要执行的持久化方法的route,持久化方法的id和持久方法的参数。其中,我们使用持久化方法的route来在之前配置的持久化方法的路径中找到对应的方法,并且将参数传入。而持久化方法的id则和其route一起,作为这个持久化命令的唯一标识,用来就行持久化方法的合并,取消等操作。 57 | 58 | ###pomelo-sync模块的应用 59 | 使用pomelo-sync模块让应用逻辑与持久层相互独立。除了在用户场景时的数据加载操作,在整个游戏运行过程中,不会再与数据库进行其他任何直接的数据交互,于场景中的所有数据,都是直接从内存中获取,从而避免了异步io带来的复杂的数据操作和事务处理。 60 | 而由于场景中的操作都是直接从内存中获取数据,因此不会直接依赖持久层的数据,对于数据的变化,我们也不需要即时保存,只需要在必要时再进行数据读写,从而减少了持久化的数据量。 61 | 62 | Pomelo-sync模块虽然集成在Pomelo框架内部,但它的使用并不是强制性的。对于需要使用其他持久化服务的用户,可以自由使用其他的持久化方案,‘sync'模块只会通过用户主动配置才会加载。 63 | 64 | ##广播消息和AOI 65 | 在上一节中,我们介绍了三种服务器与客户端之间的通讯方式requset/response模式,notify模式和广播模式。前面两种通信方式都是1:1的通信模式,一个请求只会产生一条消息。而广播模式则是1:n的,对一条广播消息来收,可能会产生n条消息,其中n表示需要广播的对象的数量。 66 | 67 | 在游戏场景中,连接服务器往往会成为性能的热点,这就是是由于巨大的广播消息所导致的。由于广播消息的数量和玩家数量相比是n^2的关系,这就会导致广播消息数量会随着玩家数量急剧增长: 68 | 69 | 假定在一个游戏场景中有100个玩家,每个玩家每秒钟产生一条需要广播的消息,如果采用全局广播的模式,那么每秒钟需要广播的消息数量就是10000条,如果玩家数目有1000人,那么广播数目就会达到1000000条,一个足够让服务器宕机的数字。因此,对于前端服务器,往往采用了服务器集群的方式,采用多台连接服务器来分担广播的压力。但是,仅仅通过增加服务器并不能从根本上解决问题。更重要的是从减少广播消息的数量着手,这就是AOI(area of interest)技术需要解决的问题。 70 | 71 | ###AOI的理论基础 72 | 在一个游戏场景中,相对于整个游戏场景来说,玩家的视野往往只占其中很小的一部分,而玩家所关心的往往也只是在他视野中所发生的事件。因此,对于绝大部分广播消息来说,我们往往只需要通知能“看到”这个事件发生的玩家就可以了。而一个玩家在游戏中的视野范围来就是AOI(area of interest)。 73 | ###Pomelo-AOI模块 74 | 在Pomelo框架中,我们提供了基于灯塔的AOI实现。其基本思路是将整个游戏地图划分为等大的区域,每个区域都有一个虚拟的灯塔,负责维护这一区域中所有在AOI范围内的实体。 75 | 所有的实体都分为观察者和被观察者两类,一个实体可以是观察者也可以是被观察者。当一个AOI事件发生时,首先会找到对应tower的所有观察者,然后发出通知。而观察者所监听的tower的范围就表示了他的视野,如下图所示: 76 | 77 | ![pomelo-aoi](./img/TowerAOI.png) 78 | 79 | 可以看到,在上图中整个场景被分为多个小的矩形,每个矩形就表示一个灯塔。屏幕中间的红点就表示游戏玩家,外围的篮框就表示他的视野范围,而篮框覆盖的9个灯塔就表示玩家监听的所有灯塔。在这9个灯塔中发生的消息,都会被玩家所监听到。 80 | 可以看到,灯塔的范围会大于玩家的实际视野,这是出于缓冲的考虑。因为在游戏中,大部分数据传输都有一定的“滞后”效果(由于游戏时钟,网络延时等不可抗力),设置较大的视野范围可以减少以致抵消这些数据滞后带来的影响。 81 | 82 | ###AOI模块分析 83 | 在实际的场景中,AOI模块对减少消息广播数量的效果是十分明显的,下面就以实际场景为例,进行简单的数据分析: 84 | 85 | 在一个长宽都为10000的游戏场景中,有1000个玩家,每个玩家的视野都是1000×1000,玩家每秒钟产生一条需要广播的消息。如果我们采用全局广播的方式,那么每秒的广播数量就是1000000条。而如果采用AOI技术,假定每个玩家的AOI范围是2000×2000,而这些玩家是平均分布的。由于AOI区域是地图大小的1/25,那么在每个玩家的AOI范围内平均只有40个人,那么每条消息的广播数量是40条。最终每秒种广播数目为40000条,只有使用AOI之前的1/25.如果我们使用更加精确的AOI范围,比如1500x1500,那么每秒的广播数量会进一步下降到22500条左右,而这个数目是单台服务器可以承受的。 86 | 87 | 当然,实际游戏场景中,玩家一般不会完全平均分布。但是,玩家同样也不会每秒都产生需要广播的消息,而一般也不需要1500×1500这么大的视野,因此这样的计算还是有现实意义的。 88 | 89 | ###AOI技术的局限性: 90 | 虽然AOI技术可以帮助我们减少消息的广播数量,这是建立在玩家平均分布的情况下的。当玩家过度密集的时候,AOI的功能就会大打折扣。最极端的情况下,当一个场景的玩家全部聚集在一个屏幕里时,AOI就会完全失效,不但无法有效减少消息广播数量,反而因为大量的AOI计算而增加系统负载。但是,需要指出的是,在这种情况下,基本是任何技术都无能为力的,出现这种情况更多的是游戏设计的问题,是需要在设计时就尽量避免的。 91 | 92 | ##总结 93 | 在本篇文章中,我们分析了Pomelo框架中游戏场景的设计和实现,并分析了游戏场景的基本组成。并且为大家介绍了场景服务中最为基础的两个功能,数据持久化和广播消息。并介绍了Pomelo中提供的解决方案和使用方法。 94 | 95 | ##参考资料 96 | 97 | [lordofpomelo demo](https://github.com/NetEase/lordofpomelo) 98 | 99 | [pomelo-sync 模块](https://github.com/NetEase/pomelo-sync) 100 | 101 | [pomelo-aoi 模块](https://github.com/NetEase/pomelo-aoi) 102 | 103 | -------------------------------------------------------------------------------- /node-game-server-5/node-game-server-5.md: -------------------------------------------------------------------------------- 1 | #pomelo框架的设计动机与架构介绍 2 | 3 | 在本系列第一篇文章中我们介绍了游戏服务器的基础、难点、框架,以及用pomelo框架解决了运行架构的问题。 随后两篇文章我们通过示例介绍了用pomelo开发聊天服务器、捡宝游戏的开发过程。 4 | 代码示例让我们对pomelo有了很好的感性认识。现在让我们回过头来详细分析一下pomelo的设计细节,架构以及未来的发展方向。 5 | 6 | ## 一、Pomelo的定义和组成 7 | 8 | 9 | 以下是Pomelo官网给出的最初定义:Pomelo是基于node.js的高性能,分布式游戏服务器框架。它包括基础的开发框架和相关的扩展组件(库和工具包),可以帮助你省去游戏开发枯燥中的重复劳动和底层逻辑的开发。 10 | 11 | Pomelo最初的设计初衷是为了游戏服务器, 不过我们在设计、开发完成后发现pomelo是个通用的分布式实时应用开发框架。它的灵活性和可扩展性使pomelo框架有了更广阔的应用范围。 由于强大的可能伸缩性和灵活性,pomelo在很多方面甚至超越了现有的开源实时应用框架。 12 | 13 | 如果你浏览一下[网易的github](https://github.com/NetEase/),会发现pomelo远远不止是一个repository, 它是由一系列松耦合的组件组合在一起的,包括了各类demo, 各类客户端,各种库和工具。 14 | 15 | 下图是pomelo最初的组成图: 16 | ![pomelo框架](http://pomelo.netease.com/resource/documentImage/pomeloFramework.png) 17 | 18 | pomelo包括以下几部分: 19 | 20 | * 框架, 框架是pomelo最核心的部分。 21 | * 库,pomelo提供了很多库,有些是跟游戏逻辑完全相关的,如[AI](https://github.com/NetEase/pomelo-bt),[AOI](https://github.com/NetEase/pomelo-aoi),[寻路](https://github.com/NetEase/pomelo-pathfinding)等;也有与游戏逻辑无关的,如[定时任务执行](https://github.com/NetEase/pomelo-scheduler), [数据同步](https://github.com/NetEase/pomelo-sync)。 22 | * 工具,pomelo提供了管理控制台、命令行工具、压力测试工具等一系列工具。 23 | * 各类客户端, pomelo提供了各类平台的客户端,包括js, C, android, iOS, unity3d等,这些都可以从pomelo的官方主页查到。 24 | * Demo, 一个框架需要强大的demo来展示功能,pomelo提供了全平台的聊天demo和[基于HTML5的捡宝Demo](https://github.com/NetEase/treasures),系统还提供了一个强大的基于HTML5开发的强大的MMO游戏demo [Lord Of Pomelo](https://github.com/NetEase/lordofpomelo)。 25 | 26 | 而最妙的地方在于所有这些组件都是松耦合的,所有这些组件都可以独立使用。这使pomelo框架异常灵活,它可 27 | 由于篇幅有限,本篇文章只讨论pomelo框架。 28 | 29 | ## 二、游戏服务器开发架构分析 30 | 31 | ### 游戏服务器运行架构的演变 32 | 33 | 网络游戏运行架构的演化可以通过下图说明: 34 | 35 | 36 | 37 | 最初的网络服务器是单进程的架构,所有的逻辑都在单台服务器内完成, 这对于同时在线要求不高的游戏是可以这么做的。由于同时在线人数的上升, 单服务器的可伸缩性必然受到挑战。 38 | 39 | 随着网络游戏对可伸缩性要求的增加,分布式是必然的趋势的。 40 | 41 | 游戏服务器的分布式架构与Web服务器是不同的, 以下是web服务器与游戏服务器架构的区别: 42 | 43 | * 长连接与短连接。web应用使用基于http的短连接以达到最大的可扩展性,游戏应用采用基于socket(websocket)的长连接,以达到最大的实时性。 44 | * 分区策略不同。web应用的分区可以根据负载均衡自由决定, 而游戏则是基于场景(area)的分区模式, 这使同场景的玩家跑在一个进程内, 以达到最少的跨进程调用。 45 | * 有状态和无状态。web应用是无状态的, 可以达到无限的扩展。 而游戏应用则是有状态的, 由于基于场景的分区策略,它的请求必须路由到指定的服务器, 这也使游戏达不到web应用同样的可扩展性。 46 | * 广播模式和request/response模式。web应用采用了基于request/response的请求响应模式。而游戏应用则更频繁地使用广播, 由于玩家在游戏里的行动要实时地通知场景中的其它玩家, 必须通过广播的模式实时发送。这也使游戏在网络通信上的要求高于web应用。 47 | 48 | 49 | 因此, Web应用与游戏应用的运行架构也完全不同, 下图是通常web应用与游戏应用的不同: 50 | 51 | 52 | 53 | 54 | 可以看到由于web服务器的无状态性,只需要通过前端的负载均衡器可以导向任意一个进程。 55 | 而游戏服务器是蜘蛛网式的架构,每个进程都有各自的职责,这些进程的交织在一起共同完成一件任务。这就是一个标准的分布式开发架构。 56 | 57 | 58 | ### 分布式开发与难点 59 | 60 | 几乎在很多书、演讲和文章中都可以看到这样的观点: 分布式开发是很难的。 如果把所有这些难点都合起来也许有好几本书,我们今天着重看来一下游戏服务器开发的难点吧。 61 | 62 | #### 多进程(服务器)的管理,重量级的架构影响开发效率 63 | 64 | 通常的游戏服务器要由很多进程共同去完成任务。当这些进程交织在一起的时候,多进程的管理并不那么容易。 65 | 66 | * 如果没有统一的抽象与管理,光把这些开发环境的进程启动起来就是非常复杂的工作, 进程的启动与重启就将严重影响开发效率。 67 | * 重量级的进程消耗大量的机器资源,普通的开发机支撑不了那么多进程,可能一个人的开发环境就需要多台机器。 68 | * 多进程间的调试并不容易, 我们发现一个bug就要跨好几个进程。 69 | 70 | 71 | #### rpc调用 72 | 73 | rpc调用的解决方案已经有n多年的历史了,但rpc在分布式开发效率上仍然没有明显提升。 74 | 75 | 以当前最流行的开发框架thrift为例,它在能调用代码前需要经过以下步骤: 76 | 77 | 78 | 如果发生接口改动,我们又需要重新修改描述文件,重新生成stub接口。对于接口不稳定的开发环境, 这种方式对开发效率影响较大。 79 | 80 | 要想让rpc调用的开发达到最简,不需生成stub接口, 无需描述文件, 我们需要一种很巧妙的方法。 81 | 82 | 83 | #### 分布式事务、异步化操作 84 | 85 | 尽管我们尽量把逻辑放在一个进程里处理,但分布式事务仍然是不可避免的。两阶段提交的代码,异步化的操作在普通的开发语言里并不是容易的事。 86 | 87 | 但我们会发现用了node.js之后,它的编程模式里天生就是这种模式, 两阶段提交、异步化操作这些看似复杂的工作里在node.js只是一个正常的异步执行流程。 88 | 89 | 90 | #### 负载均衡,高可用 91 | 92 | 由于游戏服务器的有状态性,很多请求需要通过特定的路由规则导到某台服务器;对于有些无状态的服务器,我们则可以把请求路由到负载最低的服务器。 93 | 94 | 通常对于无状态的服务器, 高可用是比较好做的。对于有状态的服务器,要做高可用会非常困难, 但也不是完全没有办法,常见的两招: 95 | 96 | * 将状态引出到外存,例如redis, 这样进程本身就可以无状态了。但由于所有的操作都通过redis可能带来性能损耗,有些场景是不能应会这些损耗的。 97 | * 通过进程互备, 将状态通过日志等方式同步到另一进程, 但这可能存在着瞬间数据丢失的问题,这种数据丢失在一些应用场景可能毫无问题, 但在另外一些应用场景可能引起严重的数据不一致。 98 | 99 | 有状态的高可用并不是那么好实现的,pomelo将在0.5版本提供高可用的实现机制,引入zookeeper和redis可以解决一些进程(如master)的高可用问题,但真正复杂的应用场景的逻辑只能由应用自己处理。 100 | 101 | #### Node.js的引入 102 | 103 | 为什么会在这里谈node.js的引入? 因为在讲了这么多分布式开发的难点之后,引入node.js实在是太自然了。它解决了分布式开发的很多问题。 104 | 105 | * 天生的分布式, node.js之所以叫node就是因为它天生就是做多进程开发的, 多个节点(node)互相通讯交织在一起组成的分布式系统是node天生就应该这么干的。例如前面提到的分布式事务、异步化操作在node.js里只是个正常的流程。 106 | * 网络io与可伸缩性的优势。游戏是非常io密集型的应用, 采用node.js是最合适的, 可达到最好的可伸缩性。 107 | * 轻量级, 轻量级的进程带来的开发效率的优势在开发的时候异常明显。 108 | * 语言优势。使用javascript开发可以实现快速迭代,客户端html 5使用javascript,甚至在unity3d,cocos2d-x这样的游戏平台上也可以使用javascript, 可实现最大限度的代码共用。 109 | 110 | 111 | 112 | ## 四、pomelo架构分析 113 | 114 | 115 | pomelo框架在最初设计的时候只为了一个目标:为基于长连接的分布式游戏服务器架构提供基础设施。框架的内容在逐渐扩展,但最核心的框架只为了干以下三件事: 116 | 117 | * 服务器(进程)的抽象与扩展 118 | 119 | 在web应用中, 每个服务器是无状态、对等的, 开发者无需通过框架或容器来管理服务器。 120 | 但游戏应用不同, 游戏可能需要包含多种不同类型的服务器,每类服务器在数量上也可能有不同的需求。这就需要框架对服务器进行抽象和解耦,支持服务器类型和数量上的扩展。 121 | 122 | * 客户端的请求、响应、广播 123 | 124 | 客户端的请求、响应与web应用是类似的, 但框架是基于长连接的, 实现模式与http请求有一定差别。 125 | 广播是游戏服务器最频繁的操作, 需要方便的API, 并且在性能上达到极致。 126 | 127 | * 服务器间的通讯、调用 128 | 129 | 尽管框架尽量避免跨进程调用,但进程间的通讯是不可避免的, 因此需要一个方便好用的RPC框架来支撑。 130 | 131 | 132 | 下面分别对这三个目标进行详细的分析: 133 | 134 | ### 服务器(进程)的抽象与扩展介绍 135 | 136 | #### 服务器的抽象与分类 137 | 该架构把游戏服务器做了抽象, 抽象成为两类:前端服务器和后端服务器, 如图: 138 | 139 |
140 | ![服务器抽象](http://pomelo.netease.com/resource/documentImage/serverAbstraction.png) 141 |
142 | 143 | 前端服务器(frontend)的职责: 144 | * 负责承载客户端请求的连接 145 | * 维护session信息 146 | * 把请求转发到后端 147 | * 把后端需要广播的消息发到前端 148 | 149 | 后端服务器(backend)的职责: 150 | * 处理业务逻辑, 包括RPC和前端请求的逻辑 151 | * 把消息推送回前端 152 | 153 | #### 服务器的鸭子类型 154 | 动态语言的面向对象有个基本概念叫鸭子类型。 155 | 服务器的抽象也同样可以比喻为鸭子, 服务器的对外接口只有两类, 一类是接收客户端的请求, 叫做handler, 一类是接收RPC请求, 叫做remote, handler和remote的行为决定了服务器长什么样子。 156 | 因此我们只要定义好handler和remote两类的行为, 就可以确定这个服务器的类型。 157 | 158 | #### 服务器抽象的实现 159 | 利用目录结构与服务器对应的形式, 可以快速实现服务器的抽象。 160 | 161 | 以下是示例图: 162 | ![目录结构](http://pomelo.netease.com/resource/documentImage/directory.png) 163 | 164 | 图中的connector, area, chat三个目录代表三类服务器类型, 每个目录下的handler与remote决定了这个服务器的行为(对外接口)。 开发者只要往handler与remote目录填代码, 就可以实现某一类的服务器。这让服务器实现起来非常方便。 165 | 让服务器动起来, 只要填一份配置文件servers.json就可以让服务器快速动起来。 166 | 配置文件内容如下所示: 167 | 168 | ```json 169 | { 170 | "development":{ 171 | "connector": [ 172 | {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort":3010, "frontend":true}, 173 | {"id": "connector-server-2", "host": "127.0.0.1", "port": 3151, "clientPort":3011, "frontend":true} 174 | ], 175 | "area": [ 176 | {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "area": 1}, 177 | {"id": "area-server-2", "host": "127.0.0.1", "port": 3251, "area": 2}, 178 | {"id": "area-server-3", "host": "127.0.0.1", "port": 3252, "area": 3} 179 | ], 180 | "chat":[ 181 | {"id":"chat-server-1","host":"127.0.0.1","port":3450} 182 | ] 183 | } 184 | } 185 | ``` 186 | 187 | 188 | ### 客户端请求与响应、广播的抽象介绍 189 | 所有的web应用框架都实现了请求与响应的抽象。尽管游戏应用是基于长连接的, 但请求与响应的抽象跟web应用很类似。 190 | 下图的代码是一个request请求示例: 191 | 192 | ![请求示例](http://pomelo.netease.com/resource/documentImage/request.png) 193 | 194 | 请求的api与web应用的ajax请求很象,基于Convention over configuration的原则, 请求不需要任何配置。 如下图所示,请求的route字符串:chat.chatHandler.send, 它可以将请求分发到chat服务器上chatHandler文件定义的send方法。 195 | 196 | Pomelo的框架里还实现了request的filter机制,广播/组播机制,详细介绍见[pomelo框架参考](https://github.com/NetEase/pomelo/wiki/Pomelo-Framework)。 197 | 198 | ### 服务器间RPC调用的抽象介绍 199 | 架构中各服务器之间的通讯主要是通过底层RPC框架来完成的,该RPC框架主要解决了进程间消息的路由和RPC底层通讯协议的选择两个问题。 200 | 服务器间的RPC调用也实现了零配置。实例如下图所示: 201 | 202 | ![rpc调用](http://pomelo.netease.com/resource/documentImage/rpcInterface.png) 203 | 204 | 上图的remote目录里定义了一个RPC接口: chatRemote.js,它的接口定义如下: 205 | ``` 206 | chatRemote.kick = function(uid, player, cb) { 207 | } 208 | ``` 209 | 其它服务器(RPC客户端)只要通过以下接口就可以实现RPC调用: 210 | ``` 211 | app.rpc.chat.chatRemote.kick(session, uid, player, function(data){ 212 | }); 213 | ``` 214 | 这个调用会根据特定的路由规则转发到特定的服务器。(如场景服务的请求会根据玩家在哪个场景直接转发到对应的server)。 215 | 216 | rpc的使用远比其它rpc框架简单好多,因为我们无需写任何配置文件,也无需生成stub。因为我们服务器抽象的实现的方式,使得rpc客户端可以在应用启动时扫描服务器目录自动生成stub对象。 217 | 218 | 219 | 完成了以上三个目标, 一个实时的分布式应用框架的轮廓就搭出来了,剩下的工作是往上添肉,这是我们后续文章里的内容。 220 | 221 | 222 | ## 五、从游戏框架到实时应用框架 223 | 224 | 当我们分析完pomelo框架的设计目标时, 我们发现核心框架的这件事情竟然与游戏没有任何关系。这是一个通用的实时分布式应用开发框架。官网上的聊天服务器demo就是一个实时应用。 225 | 226 | 事实上pomelo已经被应用在很多非游戏领域的。 网易的消息推送平台是基于pomelo开发的,它承担了网易移动端和web端的消息推送, 目前已经上线使用。 227 | 228 | 目前在开源社区最流行的实时应用框架当数meteor,它与pomelo有着截然不同的设计目标。 229 | 以下是meteor给出的定义: 230 | 231 | Meteor is an open-source platform for building top-quality web apps in a fraction of the time, whether you're an expert developer or just getting started. 232 | 233 | 可以用以下两点概括: 234 | 235 | * meteor是只能面向web的实时应用 236 | * meteor最关注的是开发效率 237 | 238 | 但是我们的问题是: 239 | 240 | * 现在的实时应用有多少是只面向web端的? 241 | * 规模稍大的实时应用,瓶颈是在可伸缩性、性能还是在开发效率? 242 | 243 | 我们给出的答案是: 244 | 245 | * 同时支持移动端、web端、PC端的实时应用已经是主流 246 | * 相比开发效率,可伸缩性、性能是规模较大的实时应用更有可能出现的瓶颈。 247 | 248 | 而pomelo在这两方面具有明显的优势: 249 | 250 | * pomelo支持动态connector协议机制,使它同时支持web、移动、PC、untiy3d等各类客户端。开发无缝衍接各类客户端的高时应用在pomelo里面只是个配置问题 251 | * pomelo在可伸缩性和扩展上具有很强的优势,这也是pomelo设计的最根本目标 252 | 253 | 254 | 255 | ## 六、未来与展望 256 | 257 | pomelo在开社区才刚刚起步, 仅仅不到半年时间pomelo已经在github和各类社区上积累了强大的人气,1700的watcher在github已经是相当不错的战绩。但这仅仅只是个起步,pomelo的真正的暴发期还未到来。 258 | 259 | pomelo将会在分布式开发方面下更大的功夫,在加强高可用、负载均衡、过载保护、运维机制等方面做得更好. 260 | 261 | pomelo也逐渐在世界的开源社区推广。LXJS 2013的组织者邀请了笔者于2013年10月去葡萄牙里斯本做英文演讲,可见pomelo已经逐渐受到了国际node社区的牛人关注。当然这还只是个开始。 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /node/img/compile_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/compile_opt.png -------------------------------------------------------------------------------- /node/img/function_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/function_1.png -------------------------------------------------------------------------------- /node/img/gc_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/gc_1.png -------------------------------------------------------------------------------- /node/img/gc_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/gc_2.png -------------------------------------------------------------------------------- /node/img/gc_graph_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/gc_graph_1.png -------------------------------------------------------------------------------- /node/img/gc_graph_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/gc_graph_2.png -------------------------------------------------------------------------------- /node/img/hidden_class_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/hidden_class_a.png -------------------------------------------------------------------------------- /node/img/hidden_class_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/hidden_class_b.png -------------------------------------------------------------------------------- /node/img/hidden_class_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/hidden_class_c.png -------------------------------------------------------------------------------- /node/img/tick_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiecc/game-server-development/f22cab875e3de6b8064c7f9f39dc159af46b431a/node/img/tick_1.png -------------------------------------------------------------------------------- /node/node.js-V8引擎相关的性能优化.md: -------------------------------------------------------------------------------- 1 | # node.js背后的引擎V8及优化技术 2 | 3 | 前一期朴灵曾介绍了node.js背后的V8引擎在内存管理方面的处理。本文将挖掘V8引擎在其它方面的代码优化,如何写出高性能的代码,及V8的性能诊断工具。V8是chrome背后的javascript引擎,因此本文的相关优化经验也适用于基于chrome浏览器的javascript引擎。 4 | 5 | node.js的执行速度远超ruby、python等脚本语言,达到接近java与C的速度, 这背后都是V8引擎的功劳。(具体性能数据可能参考朴灵的《深入浅出node.js》) 6 | 7 | 8 | ## 一、V8背后的故事 9 | 10 | ### 1.1 javascript的速度与需求 11 | 12 | JavaScript存在至少10年了。在1995年,它出现在网景(Netscape Communications)公司所研发的网页浏览器Netscape Navigator 2.0中。然而有段时间人们对于性能的要求不高,因为它只用在网页上少数的动画、交互操作或其它类似的动作上。(最明确的是为了减少网络传输,以提高效率和改善交互性!)浏览器的显示速度视网络传输速度以及渲染引擎(rendering engine)解析HTML、CSS(cascading style sheets, CSS)及其他代码的速度而定。浏览器的开发工作优先提升渲染引擎的速度,而JavaScript的处理速度不是太重要。同时出现的Java有相当大的进步,它被做得愈来愈快,以便和C++竞争。 13 | 14 | 然而,在过去几年,JavaScript突然受到广泛使用。原因是之前被当成桌面应用的软件(其中包括Office套件等),现已成为可以在浏览器中执行的软件。 15 | 16 | Google本身就推出了好几款JavaScript网络应用,其中包括它的Gmail电子邮件服务、Google Maps地图数据服务、以及Google Docs office套件。 17 | 18 | 这些应用表现出的速度不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。 19 | 20 | Chrome团队意识到javascript引擎速度的重要性, 它们可以用现有的webkit引擎开发chrome, 但是javascript引擎必须重头开发, 他们需要虚拟机与语言的专家来解决这个问题,而这个专家就是Lars Bak。 21 | 22 | 23 | ### 1.2 Lars Bak(V8引擎创始人)出山 24 | 25 | Lars Bak人首次在加州硅谷引起人们的注意是在1991年,那时他在Sun公司工作,后来成为业界最佳程序员之一。1994年,他离开Sun,帮助创建了Animorphic系统,该公司后来被Sun收购。再次回到Sun之后,Bak开发了后来成为Java HotSpot(行业标准计算系统之一)的主负责人。 26 | 27 | 可是2000年初,他却离开了计算机世界的核心,回到了丹麦,搬家是为了幸福生活,为了他的女儿们(他想让她们上丹麦语学校),为了自己的身心健康。美国的开发者社区工作很紧张,生活方式不健康。当巴克回到丹麦时,两个月之内他减了20斤(多亏了美国的阿式饮食疗法【Atkins diet】),而且再也没有反弹。 28 | 29 | 2002年,Bak在奥尔胡斯创建了一家名为OOVM的公司。2004年,他将公司卖给了一家瑞士公司Esmertec,然后又在该公司干了两年,帮助两个公司的融合。离开Esmertec时,他并不特别想找新项目:他有足够的钱养家糊口,也有各种打发时间的方式,包括粉刷农舍的计划。他估计得要一年时间。 30 | 31 | 然后Google的电话就来了。对于Google,巴克是不二选择——他编写了JavaScript引擎(Chrome的核心部分)。对于Bak,为Google工作就是 “小菜一碟”。“我不在乎当什么高级经理。我在乎的是推动技术边界。”Bak接受了这份工作,但不会回到加州。事实上他从没打算再次回加州——虽然谷歌的人性化办公室闻名远近,餐厅里的美食,还可以免费理发,Bak却宁可在家工作——离总部5000英里,相差9个时区。谷歌做好了“信任我的准备。他们知道我不会消磨时间。” Bak一开始在自己的农舍工作,后来搬到了奥尔胡斯的一座大学,google为他专门建立了研究院。 32 | 33 | Lars Bak果然不负众望,chrome于2008年推出,V8引擎成为了世界上最快的javascript引擎。V8引擎是与chrome完全剥离的,它作为单独的开源项目在google code上开源。 Bak的原意是希望其它浏览器能够参考这个引擎的实现,使所有浏览的速度都上一个台阶。然而在2008年底,V8引擎引来一个意想不到的顾客。 34 | 35 | ### 1.3 Ryan Dahl(node.js创始人)的选择 36 | 37 | Ryan Dahl就是这个意想不到的顾客。2004年的时候, Ryan Dahl还是纽约罗切斯特大学数学系的博士生, 似乎跟开发、开源社区没有任何。然而在2006年他做了一个意想不到的决定:退学,一个人拎着小包带着仅有的1000美元来到了智利的一个小镇Valparaiso。 38 | 39 | 当时他甚至都没想到糊口的办法,甚至想在智利教英语,但显然Ryan同学教英语教得并不顺,最后被带上了开发这条道路。从最初的ruby on rails应用开发变成了高性能web服务器的专家,Ryan只用了2年时间。他开始尝做开源项目来解决高并发web应用的问题,他尝试了各种语言ruby、lua、C,但所有这些都失败了。 只有用C写的http服务库libebb在变成了后来libuv的前身。 40 | 41 | 就在Ryan有点绝望的时候,V8引擎发布了,Ryan忽然灵光一闪,可以用javascript语言来实现高性能的web服务器。Ryan最后的开发成果就是node.js。2009年底当Ryan在柏林的jsconf eu演讲之后,node.js逐渐流行于世。 42 | 43 | node.js让javascript语言成为了高并发服务端开发的重要语言,也让V8引擎的应用从浏览器移到了服务端。 44 | 45 | 46 | ## 二、V8的优化技术概述 47 | 48 | V8引擎在虚拟机与语言性能优化上做了很多工作。不过按照Lars Bak的说法, 所有这些优化技术都是不他们创造的。这些技术都只是在前人的基础上做的改进。以下列一下V8的相关优化技术. 49 | 50 | ### 2.1 隐藏类(hidden class) 51 | 52 | 为了减少 JavaScript 中访问属性所花的时间,V8 采用了和动态查找完全不同的技术来实现属性的访问:动态地为对象创建隐藏类。这并不是什么新的想法,基于原型的编程语言 Self 就用 map 来实现了类似的功能(参见 An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes )。在 V8 里,当一个新的属性被添加到对象中时,对象所对应的隐藏类会随之改变。 53 | 54 | 下面我们用一个简单的 JavaScript 函数来加以说明: 55 | 56 | 57 | ``` 58 | function Point(x, y) { 59 | this.x = x; 60 | this.y = y; 61 | } 62 | ``` 63 | 64 | 当 new Point(x, y) 执行的时候,一个新的 Point 对象会被创建出来。如果这是 Point 对象第一次被创建,V8 会为它初始化一个隐藏类,不妨称作 C0。因为这个对象还没有定义任何属性,所以这个初始类是一个空类。到这个时候为止,对象 Point 的隐藏类是 C0。 65 | 66 | ![class_a](img/hidden_class_a.png) 67 | 68 | 执行函数 Point 中的第一条语句(this.x = x;)会为对象 Point 创建一个新的属性 x。此时,V8 会在 C0 的基础上创建另一个隐藏类 C1,并将属性 x 的信息添加到 C1 中:这个属性的值会被存储在距 Point 对象的偏移量为 0 的地方。 69 | 在 C0 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 x 之后能够找到 C1 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C1。 70 | 71 | ![class_b](img/hidden_class_b.png) 72 | 73 | 执行函数 Point 中的第二条语句(this.y = y;)会添加一个新的属性 y 到对象 Point 中。同理,此时 V8 会: 74 | 75 | 在 C1 的基础上创建另一个隐藏类 C2,并在 C2 中添加关于属性 y 的信息:这个属性将被存储在内存中离 Point 对象的偏移量为 1 的地方。 76 | 在 C1 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 y 之后能够找到 C2 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C2。 77 | 78 | ![class_c](img/hidden_class_c.png) 79 | 80 | 乍一看似乎每次添加一个属性都创建一个新的隐藏类非常低效。实际上,利用类转移信息,隐藏类可以被重用。下次创建一个 Point 对象的时候,就可以直接共享由最初那个 Point 对象所创建出来的隐藏类。例如,如果又一个 Point 对象被创建出来了: 81 | 82 | 一开始 Point 对象没有任何属性,它的隐藏类将会被设置为 C0。 83 | 当属性 x 被添加到对象中的时候,V8 通过 C0 到 C1 的类转移信息将对象的隐藏类更新为 C1 ,并直接将 x 的属性值写入到由 C1 所指定的位置(偏移量 0)。 84 | 当属性 y 被添加到对象中的时候,V8 又通过 C1 到 C2 的类转移信息将对象的隐藏类更新为 C2 ,并直接将 y 的属性值写入到由 C2 所指定的位置(偏移量 1)。 85 | 尽管 JavaScript 比通常的面向对象的编程语言都要更加动态一些,然而大部分的 JavaScript 程序都会表现出像上述描述的那样的运行时高度结构重用的行为特征来。使用隐藏类主要有两个好处:属性访问不再需要动态字典查找了;为 V8 使用经典的基于类的优化和内联缓存技术创造了条件。 86 | 87 | 88 | ### 2.2 内联缓存(incline cache) 89 | 90 | 在第一次执行到访问某个对象的属性的代码时,V8 会找出对象当前的隐藏类。同时,V8 会假设在相同代码段里的其他所有对象的属性访问都由这个隐藏类进行描述,并修改相应的内联代码让他们直接使用这个隐藏类。当 V8 预测正确的时候,属性值的存取仅需一条指令即可完成。如果预测失败了,V8 会再次修改内联代码并移除刚才加入的内联优化。 91 | 92 | 例如,访问一个 Point 对象的 x 属性的代码如下: 93 | 94 | ``` 95 | point.x 96 | ``` 97 | 98 | 在 V8 中,对应生成的机器码如下: 99 | 100 | ``` 101 | ebx = the point object 102 | cmp [ebx, ],