asp.net core 从单机到集群
asp.net core 从单机到集群
intro
这篇文章主要以我的活动室预约的项目作为示例,看一下一个 asp.net core 应用从单机应用到分布式应用需要做什么。
示例项目
单机版方便部署,不依赖其他环境,数据库使用的是 sqlite,详细部署文档可以参考:https://github.com/weihanli/activityreservation/blob/dev/docs/deploy/standalone.md
集群版,目前依赖的组件有 mysql(数据库)/redis(缓存)/elasticsearch(日志)
日志
日志原来是输出到文件中的,单机部署没有什么问题,可以直接 ssh 到机器上查看文件内容,但是如果部署到集群上,日志再输出到文件的话,排查起来可就有点麻烦了,日志是分散在多台机器上,只看某一台机器上的日志可能并不能解决问题。
基于日志这个痛点让我把日志迁移到 elasticsearch 上,日志统一输出到 es,并通过 kibana 来搜索/分析日志。
日志组件一直用的 log4net,日志输出到 es ,自己写了一个 es 的 appender, 但是后来越来越觉得 log4net使用起来不够灵活,后来日志组件换成了 serilog,使用 serilog 就可以方便的扩展,增加日志要记录的信息,关于自定义 serilog enricher 可以参考 serilog 自定义 enricher 来增加记录的信息
使用 es 来存储日志还有一个好处,就是搜索日志非常的快,而且借助 kibana 可以很方便的进行统计分析
拿上篇文章的图来借用一下,下面是 kibana 基于日志的 requestip 来绘制的前十个访问最多的 ip 地址
缓存
单机部署为了不增加系统复杂度,不引入外部依赖,单机版使用的是 memorycache
,
集群部署,就需要引入分布式缓存,我选择的是 redis,redis 组件是基于 stackexchange.redis 的,自己在其基础上封装了一些功能。
在我的这个示例应用中 redis 不仅仅做缓存,我还用 redis 的 hash 实现了一个类似于 asp.net 里 application 的服务,还有 redis 的发布订阅来实现一个 eventbus 来异步处理公告的浏览记录。
锁
单机环境下,我们用 lock 或者用信号量来实现资源在某一段时间内只能被一个请求拿到
多台机器环境下,我们需要一个分布式锁,上面引入了 redis,就用 redis 来实现一个分布式锁,分布式锁详细实现可以参考:
redlock
使用方式如下:
using (var redislock = redismanager.getredlockclient($"reservation:{reservation.reservationplaceid:n}:{reservation.reservationfordate:yyyymmdd}")) { if (redislock.trylock()) { var reservationfordate = reservation.reservationfordate; if (!isreservationfordateavailable(reservationfordate, isadmin, out msg)) { return false; } // ... return true; } else { msg = "系统繁忙,请稍后重试!"; return false; } }
dataprotection
微软在 .net core 下引入了 dataprotection 来保护网站的数据,你也可以用它做一些数据保护,之前做了一个简单数据保护扩展,通过 filter 来自动实现数据的加密/解密,详细信息可以参考
默认 dataprotection 的key 是保存到文件的,可能你也注意到过在 asp.net core 应用启动的时候默认会有一条日志信息如下:
多台机器同时部署的话,key 基本上就是不一样的,这样数据就不会被认为是安全的。
举个栗子,我的应用有一个后台使用 cookie 认证,cookie 会使用 dataprotection 的 key 进行加密,使用默认的 dataprotection 时,多台机器上(实际是k8s的多个pod) 的key 是不一样的,这就导致我在后台登录了之后,进入后台之后刷新一下可能就又跳转到登录界面,这是因为生成的 cookie ,对于一个服务来说是有效的,但是对于其他服务来说是无效的(key 不同,没有办法解密成功,认证失败),所以集群部署的时候,dataproection 是必须要设置的,放在一个统一的地方管理,我们上面已经引入了 redis,所以就把 dataprotection 的 key 放在 redis 中去保存(redis 服务可以做高可用,即使 redis 服务挂了也会重新生成一个 key,不会有什么影响)
使用到的包 microsoft.aspnetcore.dataprotection.stackexchangeredis
,配置方式:
// dataprotection persist in redis services.adddataprotection() .setapplicationname(applicationhelper.applicationname) .persistkeystostackexchangeredis(() => dependencyresolver.current.resolveservice<iconnectionmultiplexer>().getdatabase(5), "dataprotection-keys") ;
获取用户ip
集群部署的时候,会有网关/反向代理去转发请求,这时候直接通过 httpcontext.connection.remoteipaddress
获取到的 ip 地址就会是网关/反向代理的地址,并不是实际用户的地址,一般的反向代理软件会将真实的用户ip放在 x-forwarded-for
请求头中,转发到下游真正的服务器地址,你可以从请求中直接获取 x-forwarded-for
请求头的值,也可以使用微软提供的 forwardedheaders
中间件,配置方式:
public void configureservices(iservicecollection services) { // ... services.configure<forwardedheadersoptions>(options => { options.knownnetworks.clear(); options.knownproxies.clear(); options.forwardlimit = null; options.forwardedheaders = microsoft.aspnetcore.httpoverrides.forwardedheaders.all; }); } public void configure(iapplicationbuilder app, ihostingenvironment env) { app.useforwardedheaders(); // ... }
具体参数配置可以参考文档:
memo
其他还有一些上面并未提到,
比较常用的如 session,如果要上集群的话,也应该有相应的分布式 session,这个应用没有用到 session,所以上面没有提(之前用极验验证码的时候有用,后来换成腾讯的验证码服务之后去掉了session)
文件上传,如果是存在本地的话,也不太合适,可能需要存在一个集中的文件服务器或者云端存储如 azure blob。。(网站里的公告模块的图片上传还没改,,,打算基于 github 或者 开源中国的码云实现一个 storage )
其他暂时没想到了,想到了再补充吧。
reference
下一篇:
Linux--shell三剑客
推荐阅读
-
asp.net实现数据从DataTable导入到Excel文件并创建表的方法
-
从.NET CORE2.2升级到3.0过程及遇到的一些问题
-
浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题
-
ASP.NET Core自定义本地化教程之从文本文件读取本地化字符串
-
ASP.NET Core 数据保护(Data Protection 集群场景)下篇
-
详解ASP.NET Core 网站发布到Linux服务器
-
ASP.NET Core程序发布到Linux生产环境详解
-
ASP.NET MVC从视图传参到控制器的几种形式
-
详解ASP.NET Core部署项目到Ubuntu Server
-
浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题