当前位置: 首页 > news >正文

MicroService(Redis)

下载nuget包

 <ItemGroup><PackageReference Include="Caching.CSRedis" Version="3.8.800" /></ItemGroup>

 操作String

try
{string RedisConStr = "127.0.0.1,defaultDatabase=0,poolsize=3,tryit=0";//连接池最大为3;CSRedisClient rds = new CSRedisClient(RedisConStr);rds.Set("key1", "这是第一个");string sRsult = rds.Get("key1");rds.Append("key2", "ddfgh"); //追加,没有则生成键值对key2 : ddfghstring s = rds.GetRange("key2", 0, -1);//输出所有:ddfgh//得到valueMemoryStream ms = new MemoryStream();rds.Get("key1", ms);  //获取到字符串保存到内存流中去string result2 = Encoding.UTF8.GetString(ms.ToArray());Console.WriteLine(result2);//设置多个keyvaluerds.MSet("key3", "str3", "key4", "str4", "key5", "7");//得到多个string[] sarr = rds.MGet("key3", "key4");foreach (var sa in sarr){Console.WriteLine(sa);//str3 str4}//只对数字有用,否则报错rds.IncrBy("key5", 1);//:8//得到类型string s = rds.ObjectEncoding("key5");//int//全部清空rds.NodesServerManager.FlushAll(); //先得到旧值再设置新值string s = rds.GetSet<string>("key1", "这是第一个?");Console.WriteLine(s);
}
catch
{Console.WriteLine("error");throw new Exception("eroor");
}

操作hash

Redis 中如何保证缓存和数据库双写时的数据一致性?

无论先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议)。

或者采用串行化,可以保证强一致性。

写请求为什么更新数据库后是删除缓存而不是更新缓存?

注意看上面的图片,当有两个写请求的线程,线程一比线程二先执行,反而是线程二先执行完。这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了。

写请求时,为什么更新数据库,然后再删除缓存?

如果采用写请求,先删除缓存,再更新数据库就会出现如上图的情况,线程B读到的是老的数据,并且缓存中也保存的是老的数据。

写请求时,先更新数据,后删除缓存一定没有问题吗

可以看到一个读请求和一个写请求,读请求可能会读取到旧的数据,或者当写请求删除缓存失败,读请求会一直读取的是旧的缓存数据。只不过是这种情况,相对于其他的实现方式概率要低很多。

缓存延时双删

如果想再次降低读到旧数据的办法,用双删除策略。就是在更新数据库前,先删除一次缓存,这样可以进一步降低读到旧数据的概率,但依然会有概率读到旧数据。

上述中(延迟N秒)的时间一定要大于请求2将数据库旧数据写入redis的时间,这个时间短则几百毫秒,长则几秒,具体根据自己的业务而定。这个休眠时间 = B读取业务逻辑数据的耗时 + 几百毫秒。

原因:如果延迟时间小于请求2写入redis的时间,会导致请求1清除缓存的时机过早,请求2又会将旧的数据写入redis的尴尬。。。

删除缓存重试机制

延时双删和普通写操作的删除操作都有可能会操作失败,导致数据不一致,删除重试机制就是为了保证删除可靠性。(删除失败的key放到消息队列中)这种机制会造成大量的业务代码入侵。

// 业务更新接口(带重试机制)
public void updateUser(User user) {// 1. 更新数据库userMapper.update(user);String cacheKey = "user:" + user.getId() + ":info";try {// 2. 尝试删除缓存redisTemplate.delete(cacheKey);} catch (Exception e) {// 3. 删除失败,丢到MQ重试(业务代码入侵点)MqMessage message = new MqMessage(cacheKey, 0); // 0=初始重试次数rocketMQTemplate.send("cache-delete-retry-topic", message);log.error("删除缓存失败,已发送到MQ重试", e);}
}// MQ消费者(单独服务,无业务入侵)
@Consumer(topic = "cache-delete-retry-topic")
public void handleCacheDelete(MqMessage message) {String cacheKey = message.getCacheKey();int retryCount = message.getRetryCount();try {redisTemplate.delete(cacheKey);// 成功:确认消息,MQ删除} catch (Exception e) {if (retryCount < 3) {// 重试次数不够,延时3s重新入队message.setRetryCount(retryCount + 1);rocketMQTemplate.sendDelay("cache-delete-retry-topic", message, 3);} else {// 重试上限,告警alertService.send("缓存删除失败,key=" + cacheKey);}}
}
同步biglog异步删除缓存

重试删除缓存机制还可以,就是会造成好多业务代码入侵。通过数据库的binlog来异步淘汰key

redis进阶

Redis 中的内存淘汰机制
设置方式: config set maxmemory-policy volatile-lru

no-eviction: 禁止驱逐数据(当内存达到限制时,就报错)
allkeys-lru: 从redis 中回收最近使用最少的键
volatile-lru: 从设置了过期时间的键中,回收最近使用最少的键
allkeys-random:随机回收redis中的键
volitile-random:从设置了过期时间的键中,随机回收
volitile-ttl:从设置了过期时间的键中,回收存活时间较少的键
 

Redis.config

启动时候,通过配置文件启动

配置文件unit单位对大小写不敏感,

redis RDB

RDB方式,Redis会是通过一个fork程来做持久化的工作。fork线程先将数据写入到一个临时文件中,写入成功之后,再替换掉之前的 RDB 文件,不会影响主线程的工作,所以这种方式对Redis的性能影响很小。

save 900 1  代表 900s 之内有 1个 key发生修改,则发起内存快照保存
save 300 10  代表 300s 之内有 10个 key发生修改,则发起内存快照保存#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作 
stop-writes-on-bgsave-error yes
#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes
#rdb文件的名称
dbfilename dump.rdb
#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /var/lib/redis

save,满足规则触发一次

flushall触发一次

退出redis触发一次

redisAOF

AOF方式默认是通过一秒一次的一个后台线程 fsync 来进行数据备份操作的,操作方式是 append-only (只追加)的方式

appendfsync yes        # 开启AOF功能
appendfsync no        #  表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。由操作系统决定何时同步
appendfsync always    #  每次有数据修改时都会写入AOF文件
appendfsync everysec   # 每秒同步一次,该策略为AOF的缺省策略#aof文件名, 保存目录由 dir 参数决定
appendfilename "appendonly.aof"
# AOF文件重写时,是否需要将写指令通过追加的方式追加到原有的AOF文件中。
#(yes只写入到内存缓冲区,no写入到内存缓冲区同时追加到原有AOF文件)
no-appendfsync-on-rewrite no#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,
#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在
redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异
常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数
据。
如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果
是no,用户必须手动redis-check-aof修复AOF文件才可以。
aof-load-truncated yes  #重写 / 宕机后的容错配置

shuatdown。清除rdb文件,手动破坏aof文件

redis-check-aof修复aof

总结

RDB是保存快照,手动save,或者条件触发,将内存数据保存为rdb文件,替换旧的快照,可能丢失。

AOF是记录写命令,按照保存策略,由子进程进行保存,通常按照appendfsync everysec:每秒同步 1 次(默认,性能和安全折中)

维度RDBAOF
存储内容内存数据的二进制快照(数据本身)写操作命令的文本日志(操作过程)
文件大小小(压缩后)大(命令日志,易膨胀)
恢复速度快(直接加载二进制数据)慢(需要重新执行所有命令)
数据安全性可能丢失数据(丢失两次快照间的修改)丢失数据少(取决于同步策略,默认最多丢 1 秒)
写性能好(仅 fork 子进程时短暂阻塞,后续不影响)略差(需追加命令到文件,同步磁盘有开销)
兼容性支持所有 Redis 版本部分旧版本不兼容新命令格式
运维成本低(无需额外处理文件膨胀)中等(需关注 AOF 重写,避免文件过大)

发布订阅

 

Redis主从复制

启动redis

查看信息

认老大

主机也显示有从机

真实的主从配置应该在配置文件中配置,这样的话是永久的,我们这里使用的是命令,暂时的。

如果命令行配置的从机,宕机后重启会变成主机模式。

细节:

主机可以写,从机只能读不能写。   

成成递进

把81的主机配成80就行

某朝篡位,从机变主机

老大断开连接,使用Slaveof no one让自己变成主机,其他节点手动连接到这个最新的主节点,如果老大修复,就重新连接。

哨兵模式

在kconfig目录下,创建一个哨兵文件,例如sentinel1.config

sentinel monitor mymaster 192.168.200.128 6379 1 
sentinel down-after-milliseconds mymaster 10000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
# mymaster 主节点名,可以任意起名,但必须和后面的配置保持一致。
# 192.168.200.129 6379 主节点连接地址。
# 1:quorum(仲裁数) 最少需要 1 个哨兵节点判断 “主节点不可用”,才能正式认定主节点故障(触发后续故障转移)。
# sentinel down-after-milliseconds mymaster 10000:设置Sentinel认为服务器已经断线所需的毫秒数。
# sentinel failover-timeout mymaster 60000:设置failover(故障转移)的过期时间。60s内故障转移没完成就失败就隔60s再试。
# sentinel parallel-syncs mymaster 1 设置在执行故障转移时, 最多可以有多少个从服务器同时对
新的主服务器进行同步。

6379宕机,故障转移

如果曾经的6379恢复了,ta只能被归并到新的主机下,当做从机,这就是哨兵模式规则

哨兵模式确定,扩容太麻烦

Redis缓存穿透和雪崩

缓存雪崩:缓存同一时间大面积的失效,后面的请求都会落到数据库上,造成数据库短时间内承受大量的数据请求
        解决方案:缓存数据的过期时间随机设置,防止同一时间大量的数据过期的情况发生
缓存穿透:是指缓存和数据库中都没有数据,导致所有的请求都落到数据库上。数据库短时间承受大量的请求而崩掉
        缓存取不到,数据库也娶不到的数据, 设置为key—null 的方式
        布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap 中去
缓存击穿: 高并发查询同一条数据,但是redis 中的数据过期了。导致请求发送到了数据库,造成缓存击穿
        解决方案: 设置热点数据永不过期;
        使用互斥锁。分布式情况下使用分布式的锁

redis分布式锁

小徐同学开发一个抢购功能

但用户太多,服务器压力太大,小徐就使用nginx进行水平扩展服务,又出现超卖问题

因为同步锁只能锁住单个进程,其他进程还能请求。小徐发现有分布式锁这个技术,就用redis实现分布式锁,使用setnx。一个请求进来,setnx,其他请求进来发现有值返回false。不要忘记设置过期时间,因为如果请求服务器挂掉,没有过期时间,会阻塞其他请求。

但是问题又来了,如果请求业务时间大于过期时间,请求二在请求1未处理完还没有释放锁的情况下,发现setnx返回true(因为setnx过期了),就继续抢购。

问题:当2锁过期,线程还处理业务中,2当处理完业务,释放其他线程的锁,

解决:加长时间,添加子线程每10s确认一下线程是否在线,在线重置过期时间。给锁加UUID

using StackExchange.Redis;
using System;
using System.Threading;
using System.Threading.Tasks;namespace RedisDistributedLockDemo
{/// <summary>/// Redis 分布式锁(支持自动续期、防误删、原子操作)/// </summary>public class RedisDistributedLock : IDisposable{#region 私有字段private readonly IDatabase _redisDb; // Redis 数据库对象private readonly string _lockKey; // 锁的 Keyprivate readonly string _clientId; // 客户端唯一标识(防误删)private readonly TimeSpan _lockExpiry; // 锁初始过期时间private readonly TimeSpan _renewInterval; // 续期间隔(建议为过期时间的 1/3)private readonly CancellationTokenSource _cts = new(); // 取消令牌(控制续期线程)private bool _isLocked; // 是否成功获取锁#endregion#region 构造函数/// <summary>/// 初始化分布式锁/// </summary>/// <param name="redisConnection">Redis 连接(ConnectionMultiplexer)</param>/// <param name="lockKey">锁的 Key(如:"stock_lock_1001")</param>/// <param name="lockExpiry">锁初始过期时间(默认 30 秒)</param>/// <param name="renewInterval">续期间隔(默认 10 秒,建议为过期时间的 1/3)</param>public RedisDistributedLock(IConnectionMultiplexer redisConnection,string lockKey,TimeSpan? lockExpiry = null,TimeSpan? renewInterval = null){_redisDb = redisConnection.GetDatabase();_lockKey = lockKey ?? throw new ArgumentNullException(nameof(lockKey));_clientId = Guid.NewGuid().ToString("N"); // 生成唯一标识(无分隔符的 Guid)_lockExpiry = lockExpiry ?? TimeSpan.FromSeconds(30);_renewInterval = renewInterval ?? TimeSpan.FromSeconds(10);// 校验参数合理性(续期间隔不能大于过期时间)if (_renewInterval >= _lockExpiry){throw new ArgumentException("续期间隔必须小于锁过期时间");}}#endregion#region 核心方法:加锁(异步)/// <summary>/// 尝试获取锁(异步)/// </summary>/// <param name="waitTimeout">获取锁的最大等待时间(默认 0 秒,即不等待)</param>/// <param name="cancellationToken">取消令牌</param>/// <returns>true=获取成功,false=获取失败</returns>public async Task<bool> TryLockAsync(TimeSpan waitTimeout = default,CancellationToken cancellationToken = default){var endTime = DateTimeOffset.UtcNow.Add(waitTimeout); // 等待截止时间// 循环尝试获取锁(直到超时或成功)do{// 核心加锁命令:SET key value NX PX milliseconds// NX:仅当 key 不存在时设置(保证唯一锁)// PX:设置过期时间(防止锁泄露)var lockResult = await _redisDb.StringSetAsync(key: _lockKey,value: _clientId,expiry: _lockExpiry,when: When.NotExists, // 等价于 NXflags: CommandFlags.None);if (lockResult){_isLocked = true;// 启动后台续期任务(异步,不阻塞当前线程)_ = StartRenewTaskAsync();return true;}// 未获取到锁,短暂休眠后重试(避免频繁请求 Redis)await Task.Delay(100, cancellationToken); // 100 毫秒重试一次// 检查是否超时或取消} while (DateTimeOffset.UtcNow < endTime && !cancellationToken.IsCancellationRequested);return false; // 超时或取消,获取锁失败}#endregion#region 核心方法:释放锁(异步)/// <summary>/// 释放锁(异步,原子操作)/// </summary>public async Task UnlockAsync(){if (!_isLocked) return;try{// Lua 脚本:校验锁的持有者是否是当前客户端,是则删除(原子操作)const string unlockScript = @"if redis.call('GET', KEYS[1]) == ARGV[1] thenreturn redis.call('DEL', KEYS[1]) -- 释放锁elsereturn 0 -- 不是自己的锁,不操作end";// 执行 Lua 脚本(KEYS[1] = _lockKey,ARGV[1] = _clientId)await _redisDb.ScriptEvaluateAsync(script: unlockScript,keys: new RedisKey[] { _lockKey },values: new RedisValue[] { _clientId });}finally{_isLocked = false;_cts.Cancel(); // 取消续期任务}}#endregion#region 内部方法:启动续期任务/// <summary>/// 后台续期任务(自动延长锁的过期时间)/// </summary>private async Task StartRenewTaskAsync(){try{while (!_cts.IsCancellationRequested && _isLocked){// 等待续期间隔(如 10 秒)await Task.Delay(_renewInterval, _cts.Token);if (!_isLocked) break;// 续期命令:SET key value XX PX milliseconds// XX:仅当 key 存在时设置(避免创建新锁)// PX:更新过期时间await _redisDb.StringSetAsync(key: _lockKey,value: _clientId,expiry: _lockExpiry,when: When.Exists, // 等价于 XXflags: CommandFlags.None);}}catch (OperationCanceledException){// 续期任务被取消(正常释放锁场景),忽略异常}catch (Exception ex){// 记录续期失败日志(如:Redis 连接异常)Console.WriteLine($"锁续期失败,Key: {_lockKey},异常:{ex.Message}");}}#endregion#region IDisposable 释放资源public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (disposing){_cts.Cancel();_cts.Dispose();// 异步释放锁(非阻塞,避免 Dispose 等待)_ = UnlockAsync();}}~RedisDistributedLock() => Dispose(false);#endregion}
}

http://www.dtcms.com/a/599130.html

相关文章:

  • 昆明建设局网站代账会计在哪里找
  • 江门骏科网站建设小程序招商加盟
  • thinkadmin后台列表页面展示多图,点击放大镜预览效果
  • 电源完整性10-安装电感与自谐振频率
  • 360搜索网站提交入口wordpress调用文章摘要
  • 基于springboot个人云盘管理系统【带源码和文档】
  • 建商城网站带app多少钱电商数据网站
  • cms网站是什么网站根目录多文件
  • Windows 安装MySQL 9.5
  • leetcode 946 验证栈序列
  • 使用admin api添加kong配置信息
  • 怎么做狼视听网站东莞人才网官方网站
  • 厦门市建设执业资格注册管理中心网站书怎么做pdf下载网站
  • 网站制作定制图苏州工业园区招聘官网
  • 评估工程正成为下一轮Agent演进的重点
  • 哪个网站做相册好苏州网站优化建设
  • 百度账号购买网站引流推广方式
  • 做网站开公司太原建站的模板
  • vs python 网站开发优秀网站管理员
  • 西安哪里有做网站的中铁建设集团官方网站
  • 好看的手机网站推荐广州网站建设商家
  • 电商网站建设小强wordpress上传失败
  • 装饰工程设计东莞网站建设展示型网站设计与制作团队
  • OpenRefine:一款免费开源、功能强大的数据清洗工具
  • 工控人如何做自己的网站运动网站建设主题
  • TensorRT笔记(2):解析样例中Logger日志类的设计
  • 南京领动做网站怎么样佛山制作网站公司推荐
  • 苏州建网站哪个好做定制的网站
  • 江苏网站建设流程朔州市建设监理公司网站
  • AI销冠是什么?熊猫智汇公司如何利用它提升企业效率?