{"action":"closed","issue":{"url":"https://api.github.com/repos/plantain-00/blogs/issues/8","repository_url":"https://api.github.com/repos/plantain-00/blogs","labels_url":"https://api.github.com/repos/plantain-00/blogs/issues/8/labels{/name}","comments_url":"https://api.github.com/repos/plantain-00/blogs/issues/8/comments","events_url":"https://api.github.com/repos/plantain-00/blogs/issues/8/events","html_url":"https://github.com/plantain-00/blogs/issues/8","id":130373182,"number":8,"title":"web后端结构扩展方向","user":{"login":"plantain-00","id":7639395,"avatar_url":"https://avatars.githubusercontent.com/u/7639395?v=3","gravatar_id":"","url":"https://api.github.com/users/plantain-00","html_url":"https://github.com/plantain-00","followers_url":"https://api.github.com/users/plantain-00/followers","following_url":"https://api.github.com/users/plantain-00/following{/other_user}","gists_url":"https://api.github.com/users/plantain-00/gists{/gist_id}","starred_url":"https://api.github.com/users/plantain-00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/plantain-00/subscriptions","organizations_url":"https://api.github.com/users/plantain-00/orgs","repos_url":"https://api.github.com/users/plantain-00/repos","events_url":"https://api.github.com/users/plantain-00/events{/privacy}","received_events_url":"https://api.github.com/users/plantain-00/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2016-02-01T14:16:53Z","updated_at":"2016-08-02T13:40:16Z","closed_at":"2016-08-02T13:40:16Z","body":"#### web应用\r\n\r\n这是一个起点。\r\n几乎所有的语言都可以用来做web后端，目前最流行的应该是php和java，而ruby、python、node、C#等等也有很多人在用。\r\n目前的趋势是，不在服务器端渲染页面，使用RESTful API，使用轻量级的框架。\r\n\r\n#### 常规数据库\r\n\r\n大部分web应用都会有存储需求，常用的有mysql，也有用mariadb或postgresql。\r\n\r\n#### NoSQL\r\n\r\n在数据库查询速度方面，NoSQL有优势。\r\n除此之外，NoSQL数据库的设计思路也和普通数据库的不一致，不再严格遵守三个范式，允许数据冗余，也适合存储结构不固定的数据，所以可以用来弥补常规数据库的缺陷。\r\n常见的NoSQL数据库有mongodb。\r\n\r\n#### 缓存\r\n\r\n在适当的地方使用缓存，可以有效提高查询性能。\r\n常用的缓存工具有memcache、redis。\r\n对于被动缓存，例如某个数据1秒内有1000个请求，第一个请求获取到数据并把数据存入过期时间1秒的缓存，其它请求就可以直接从缓存中读取了。对于主动缓存，系统启动后就开始读取数据到缓存，数据更新时，主动更新缓存中的数据。\r\n\r\n#### 服务层\r\n\r\n当某些功能会被多个web应用调用，可以被抽出到新的服务层，web应用通过rpc的方式调用这些服务。\r\nrpc方式可以用直接基于TCP协议的框架，也可以用基于http的框架，数据格式可以是自定义、XML、json，也可以是RESTful API的形式。\r\n另外，对于node，CPU密集型任务也应该被抽离成服务，以避免影响并发。\r\n从这个角度看，数据库和缓存可以认为是数据库服务和缓存服务。\r\n但服务层里服务越来越多，服务间的相互调用也越来越多，可以使用协调工具来管理这些服务，如zookeeper。\r\n\r\n#### 推送\r\n\r\n可以使用node.js的socket.io实现。\r\nsocket.io实现的WebSocket协议，不只提供了基础的消息广播功能，也提供了更轻量级的方法使得客户端可以向服务器端发送信息。\r\n为了保证推送质量，需要客户端在接收到推送后发送反馈。\r\nsocket.io连接示例：\r\n```js\r\n// server\r\nimport * as express from \"express\";\r\nimport * as socketIO from \"socket.io\";\r\n\r\nconst app = express();\r\nconst server = app.listen(3000);\r\nconst io = socketIO(server);\r\nconst promotions = io.of(\"/promotions\");\r\n\r\npromotions.on(\"connection\", socket => {\r\n\r\n});\r\n```\r\n```html\r\n// client\r\n<script>\r\n    var socket = io(\"http://localhost:3000/promotions\");\r\n</script>\r\n```\r\n加入基于cookie的身份验证后：\r\n```js\r\n// server\r\nimport * as express from \"express\";\r\nimport * as socketIO from \"socket.io\";\r\n// changes start\r\nimport * as cookie from \"cookie\";\r\nimport * as validator from \"validator\";\r\nimport * as services from \"./services\";\r\n// changes end\r\n\r\nconst app = express();\r\nconst server = app.listen(3000);\r\nconst io = socketIO(server);\r\nconst promotions = io.of(\"/promotions\");\r\n\r\npromotions.on(\"connection\", socket => {\r\n    // changes start\r\n    const cookies = libs.cookie.parse(validator.trim(socket.handshake.headers.cookie));\r\n    services.validate(cookies[\"cookie_name\"]).then(userId => {\r\n        if (!userId) {\r\n            socket.disconnect(true);\r\n        }\r\n    });\r\n    // changes end\r\n});\r\n```\r\n加入广播功能后：\r\n```js\r\n// server\r\nimport * as express from \"express\";\r\nimport * as socketIO from \"socket.io\";\r\nimport * as cookie from \"cookie\";\r\nimport * as validator from \"validator\";\r\nimport * as services from \"./services\";\r\n\r\nconst app = express();\r\nconst server = app.listen(3000);\r\nconst io = socketIO(server);\r\nconst promotions = io.of(\"/promotions\");\r\n\r\npromotions.on(\"connection\", socket => {\r\n    const cookies = libs.cookie.parse(validator.trim(socket.handshake.headers.cookie));\r\n    services.validate(cookies[\"cookie_name\"]).then(userId => {\r\n        if (!userId) {\r\n            socket.disconnect(true);\r\n        // changes start\r\n        } else {\r\n            socket.join(\"room_name\");\r\n        }\r\n        // changes end\r\n    });\r\n});\r\n\r\n// changes start\r\nconst promotion = {\r\n\r\n};\r\npromotions.to(\"room_name\").emit(\"promotion\", promotion);\r\n// changes end\r\n```\r\n```js\r\n// client\r\nsocket.on(\"promotion\", promotion => {\r\n\r\n});\r\n```\r\n加入客户端到服务器端的通信后：\r\n```js\r\n// server\r\nimport * as express from \"express\";\r\nimport * as socketIO from \"socket.io\";\r\nimport * as cookie from \"cookie\";\r\nimport * as validator from \"validator\";\r\nimport * as services from \"./services\";\r\n\r\nconst app = express();\r\nconst server = app.listen(3000);\r\nconst io = socketIO(server);\r\nconst promotions = io.of(\"/promotions\");\r\n\r\npromotions.on(\"connection\", socket => {\r\n    const cookies = libs.cookie.parse(validator.trim(socket.handshake.headers.cookie));\r\n    services.validate(cookies[\"cookie_name\"]).then(userId => {\r\n        if (!userId) {\r\n            socket.disconnect(true);\r\n        } else {\r\n            socket.join(\"room_name\");\r\n            // changes start\r\n            socket.on(\"promotion accepted\", promotionId => {\r\n\r\n            });\r\n            // changes end\r\n        }\r\n    });\r\n});\r\n\r\nconst promotion = {\r\n\r\n};\r\npromotions.to(\"room_name\").emit(\"promotion\", promotion);\r\n```\r\n```js\r\n// client\r\nsocket.on(\"promotion\", promotion => {\r\n    // changes start\r\n    socket.emit(\"promotion accepted\", promotion.id);\r\n    // changes end\r\n});\r\n```\r\n对于多socket.io节点，可以使用https://github.com/socketio/socket.io-redis ：\r\n```js\r\n// server\r\nimport * as express from \"express\";\r\nimport * as socketIO from \"socket.io\";\r\nimport * as cookie from \"cookie\";\r\nimport * as validator from \"validator\";\r\nimport * as services from \"./services\";\r\n// changes start\r\nimport * as socketioRedis from \"socket.io-redis\";\r\n// changes end\r\n\r\nconst app = express();\r\nconst server = app.listen(3000);\r\nconst io = socketIO(server);\r\nconst promotions = io.of(\"/promotions\");\r\n// changes start\r\nio.adapter(socketioRedis({\r\n    host: \"127.0.0.1\",\r\n    port: 6379,\r\n}));\r\n// changes end\r\n\r\npromotions.on(\"connection\", socket => {\r\n    const cookies = libs.cookie.parse(validator.trim(socket.handshake.headers.cookie));\r\n    services.validate(cookies[\"cookie_name\"]).then(userId => {\r\n        if (!userId) {\r\n            socket.disconnect(true);\r\n        } else {\r\n            socket.join(\"room_name\");\r\n            socket.on(\"promotion accepted\", promotionId => {\r\n\r\n            });\r\n        }\r\n    });\r\n});\r\n\r\nconst promotion = {\r\n\r\n};\r\npromotions.to(\"room_name\").emit(\"promotion\", promotion);\r\n```\r\n```js\r\n// client\r\nsocket.on(\"promotion\", promotion => {\r\n    socket.emit(\"promotion accepted\", promotion.id);\r\n});\r\n```\r\n\r\n#### 全文搜索\r\n\r\n#### 消息队列\r\n\r\n消息队列可以实现异步调用，也可以缓冲任务。\r\n除了各种MQ，redis也可以作为轻量级的消息队列。\r\n也可以使用分布式的https://github.com/apache/kafka 。\r\n\r\n#### 业务拆分和横向扩展\r\n\r\n拆分业务后，可以简化系统复杂度。横向扩展可以分摊负载。\r\n对于session不能共享的问题，可以通过把session内容保存到缓存中来解决。\r\n\r\n#### 数据库的主从复制\r\n\r\n考虑到读操作的频率要远大于写操作，读写分离后，可以大幅减轻读库压力，还方便扩展。\r\n\r\n#### 分库和分表\r\n\r\n#### 异地双机热备\r\n\r\n为了解决机房断电断网后的系统可用性问题，可以设计异地双机热备方案，各种服务都有能够相互同步。"}}
{"actor":{"display_login":"plantain-00"}}