跟新于 2022-11日
用redis 主要是为了 以后进行,多个数据库写入。
例如我搭建一个 别的数据库论坛,我直接拿数据去redis里面拿,就不用跨库查询然后,加载到内存里面,以此类推 (比如我现在存的hash 是一个月度数据,和每日新增的数据 和修改的数据,这样我就可以直接传递不同参数写入不同数据库只需要定义 pipline 方法即可,不需要再查一下 在循环!)
1、每天日常抓取数据写入的时候,之前没有处理Href链接,导致要查询一次富文本内容,去提取Href,(查一个月度的数据,特别卡,花费要五十多秒)
2、限流问题,之前被别人Doss攻击了导致服务器停止了一个月(我的服务器是轻量云的 每月只有1T多一点)。
3、爬虫挂了没有通知,之前爬虫挂了一个多星期我没有发现,数据没跟新一周,直接亏了!
1、(打算对接一个stmp 邮件服务,不过好像服务器端口不支持,暂定把,邮箱服务通知-Net)
2、现在写入task 任务是使用写入task表,完成一个任务就去修改task表中的 State 状态,少量数据还可以,数据多了,或者多开几个线程,就有问题了(直接数据库顶不住了,当然我这个数据库,装在服务器上,一大把服务都在上面 性能有限)
日常数据增量问题
解决
1、开始用了一个比较呆的方法,使用SQL INSERT INTO SELET 语句去 写入一个新表(只需要字段,标题、Href、Id),最近发现这样去执行,效率不是很理想,如果后续数据累加,速度上比较慢
最近突然想到一个方法,使用 redis
使用Hset 进行存每日Insert 写入的数据 和 Update 的数据,这样就不用每次去删表,
实际使用效果非常好,(之前我写好了一个RabbitMQ的生产和消费,打算用定时任务每日去巡检,新增的然后写入到MQ中,然后专门写个消费者,定时去pull消费,我发现太大材小用了,就好比拿着 大刀去削个苹果,=-=)
可以看到Redis 写入成功 这里我扩展了 post的序列化 和反序 操作,注意(因为抓取的数据来自不同的网站,可能会存在重复的数据 Id,我这里处理的方法是我这边生成的一个Base64加密 把数据Id + 抓取的网站Url路由 生成一个 HashId) 但是最近发现有的网站会换域名,如果发现不及时,导致还是有很小的部分重复,(这个重复暂时没想到解决办法!)
使用 BackgroundService + IMediator
首先我们要声明一个 返回IMedator 进行注入
点击查看代码
public class TaskCacheService : BackgroundService
{
private readonly ILogger<TaskCacheService> _logger;
private readonly IServiceProvider _provider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(1000, stoppingToken);
RecurringJob.AddOrUpdate(() => RedisCache(), Cron.Daily(1));
throw new System.NotImplementedException();
}
/// <summary>
/// 写入redis 任务
/// </summary>
/// <returns></returns>
public async Task RedisCache()
{
await GetMediator().Send(new PostRedisRequest { StartTime = DateTime.Now.AddDays(-1).Date });
}
private IMediator GetMediator()
{
return _provider.CreateScope().ServiceProvider.GetService<IMediator>();
}
}
大概现在有 19W 条数据,
(后面使用go的框架 重写了一版 )
使用的 框架(go_spider) ==> https://gitee.com/huhuzhu/go_spider
go_spider =(这个功能很多用不到我自己取消了 部分功能)
ORM使用的是(Confluence) ==> https://goframe.org/pages/viewpage.action?pageId=1114245
另外使用了一个 第三方的http 库 http.Client ==> https://gitee.com/guonaihong/gout
前台使用的vue --> vue(大部分别人帮我写的,比如组件封装之类的,接口什么的是我自己调的)
http://xh.bozaiq.cn/ 每天早晨6点会更新一批数据
后台使用 net5 + ef core (开始是想代码实现读写分离,发现数据太少了 看不到效果,后面在慢慢爬取一点)
部署在docker 中,(第一次尝试部署在docker中踩了很多坑)
使用的ef core 框架
因为加入了 一个访问日志 ==》 https://www.bozaiq.cn/d/100-logmiddleware 感觉第一次加载特别慢
本来想用redis 缓存前十页,用redis存储List的话还需要进行jsonConvert 序列化 和反序列化操作,
我就用的微软推荐的 Microsoft.Extensions.Caching.Memory ==>https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/memory?view=aspnetcore-6.0
使用redis缓存了文章详情页,因为是收集网上的基本上是不会改动这个文章页 默认缓存(标识字段,IsCache = true)
swagger 地址 http://xh.bozaiq.cn:8084/swagger/index.html (jwt登陆 =》 todo - 权限管理)
这里Controller 用的是 using MediatR 个包
中介者(Mediator)模式:用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要显示的相互引用,从而使得耦合松散,而且可以独立的改变他们之间的交互
可以看到Controller 是非常明了的只需要定义请求和返回 (abpNET5)
具体的功能实现 自己定义一个 Handler文件夹
这里的PagedAsync() 这个是自己扩展的一个静态的方法 意思就是 分页并异步(因为框架比较模块设计 每个模块都可以根据需求自己扩展)
CancellationToken 这里 对长时间阻塞调用的异步取消令牌应用,在某些场景中,我们需要请求外部的第三方资源,但是由于网络等原因,可能会造成长时间的等待以致业务超时退出,这种情况可以使用 CancellationToken 来进行优化,但请求超过指定时长后退出,而不必针对每个 HttpClient 进行单独的超时设置
CancellationToken 一般.netCore 它和取消异步任务相关的 可以参考:https://www.cnblogs.com/fanfan-90/p/12660996.html
线上的total 我给屏蔽了,实际是每天会自动跟新一批数据 每天自动抓取大概几千条左右
加入了 限流中间件和显示PageSize 大小,防止爬虫
点击查看代码
``` C#
public class HomeHandler : IRequestHandler>
{
private readonly IRepository _postsRepository;
private readonly IRepository _disRepository;
private readonly IMapper _mapper;
public HomeHandler(IRepository<Posts, int> postsRepository, IRepository<Discussions, int> disRepository, IMapper mapper)
{
_postsRepository = postsRepository;
_disRepository = disRepository;
_mapper = mapper;
}
public async Task<PagedResultDto<GetPostItem>> Handle(GetPostRequest request, CancellationToken cancellationToken)
{
// 查询条件
var postquery = _postsRepository.GetQuery();
var disquery = _disRepository.GetQuery();
var queries = from post in postquery
join dis in disquery
on (int)post.DiscussionId equals dis.Id
select new GetPostItem
{
Id = post.Id,
Type = post.Type,
Content = post.Content,
Title = dis.Title,
CreateOn = post.CreateOn,
UpdateOn = post.UpdateOn
};
var query = queries.WhereIf(!string.IsNullOrEmpty(request.Keyworks), a => a.Title.Contains(request.Keyworks) || a.Content.Contains(request.Keyworks));
//查询数据库
var items = await query.PagedAsync(request);
var total = await query.CountAsync();
var pageTotal = items.Count();
return new PagedResultDto<GetPostItem>(items, total, pageTotal);
}
}
/// <summary>
/// 请求类型
/// </summary>
public class GetPostRequest : PagedRequest, IRequest<PagedResultDto<GetPostItem>>
{
public string Keyworks { get; set; }
}
/// <summary>
/// 返回类型
/// </summary>
public class GetPostItem : EntityDto<int>
{
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 内容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 状态:-1-删除 0-禁用 1-正常
/// </summary>
public EntityStatusEnums Status { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateOn { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdateOn { get; set; }
}
</details>
#### EFCore --> sql控制台日志
```C#
public static readonly ILoggerFactory MyLogFactory = LoggerFactory.Create(build =>
{
build.AddConsole(); // 用于控制台程序的输出
// build.AddDebug(); // 用于VS调试,输出窗口的输出
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(MyLogFactory);
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章