最终基本上实现了这个功能,当时也做了一个基于 SpringBoot 的 Demo ,后来又改了一版基于 Jedis 版本的,因为可以自己定义和操作多线程,更加灵活自由的实现一些细节。
最近提离职后正在交接期,自己的时间比较多,又翻出这个早起的 Demo ,想改造一下,用到我的心情记录员项目中,如果精力和时间有的话我更想将它完善为一个完整的、可复用的项目。
心情记录员小程序为什么要用到这个?以及为什么不用其他成熟的 RabbitMQ ,首先不用其他成熟的原因是我不想搞太多中间件,一个项目中 Redis 安装是大多数情况,因此我想在保持单体应用的基础上实现一个 MQ ,另外的一个原因就是折腾,这也是我本人的一个“缺点”,什么都喜欢自己折腾一下,验证可行性。接下来说说应用场景吧,心情记录员这款小程序目前调用的 AI 是 Kimi 的平台,目前因为是前期,因此是免费版本,并发数限制到了 1 ,这就会导致一个问题,如果同时有两个或者多个(意淫一下,哈哈哈)同时点了记录的时候,这时候有一个肯定会报错,这时候就可以用到消息队列了,将请求的数据先放入队列,等前一个 AI 生成结束后再次调用,就这?……,一个队列不就搞定了吗?当然还有场景了,那就是目前 AI 生成我采用的是 Stream 流式返回,而我是在点击记录后就开始生成,点击记录后会跳转到结果页面,为了使用户跳过去立马就感觉到在生成,因此我就将调用 AI 接口的动作提前到跳转页面前点击记录按钮后,然后给一定到 Loading 延迟在跳转,跳转到结果页后建立 WebSocket 连接,根据 openId 标识接收消息,这时候这个消息其实已经在上一步中在生成了,这里就可以用到消息队列了,根据对应的 openId 到消息队列中取内容,服务端的 WebSocket 只需要根据标识从消息队列中缓存的生成结果进行返回即可,提升了响应速度。
大概流程就像下图这样:
4 年前这个项目的库和博客:
Gitee-redismq (原谅我以前使用 Gitee 的行为,哈哈哈)
]]>这两个系统虽然几乎没什么人用了,但需要的时候可以自取,已开源: https://github.com/Jobcrazy/Redis-WinLegacy
]]>如题
]]>HEXPIRE
and HPEXPIRE
:设置秒级和毫秒级生存时间HEXPIREAT
and HPEXPIREAT
:设置秒级和毫秒级 UNIX 过期时间戳HPERSIST
:移除过期时间HEXPIRETIME
and HPEXPIRETIME
:获取秒级和毫秒级 UNIX 过期时间戳HTTL
and HPTTL
:获取秒级和毫秒级生存时间https://github.com/redis/redis/releases/tag/7.4-rc1
这么多年了,终于来了
]]>难道每次执行什么 get,set 操作,都会检测一遍密码吗? 我理解不能像 mysql 一样,有个连接池,初始化一些长连接,之后就不用再认证/鉴权什么的了
难道要用 C# 跑测试才能发挥 Garnet 的性能? https://microsoft.github.io/garnet/docs/benchmarking/results-resp-bench
]]>Timeout awaiting response (outbound=1KiB, inbound=0KiB, 5728ms elapsed, timeout is 5000ms), command=SET, next: EVAL, inst: 0, qu: 0, qs: 15, aw: False, bw: SpinningDown, rs: ReadAsync, ws: Idle, in: 76, in-pipe: 0, out-pipe: 0, serverEndpoint: 127.0.0.1:6379, mc: 1/1/0, mgr: 10 of 10 available, clientName: AppProductionEnvServer1(SE.Redis- v2.5.43.42402), IOCP: (Busy=0,Free=10000,Min=9000,Max=10000), WORKER: (Busy=236,Free=32531,Min=10000,Max=32767), POOL: (Threads=236,Queueditems=50,Completeditems=8751117254), v: 2.5.43.42402 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)
出错的时候看了下也就三百多并发,比较怀疑是下面这段代码引起的:
while (!await redis.GetDatabase().LockTakeAsync($"PlaceOrder:{user.UserId}", "1", TimeSpan.FromSeconds(180))) { await Task.Delay(1); }
作用是确保同一用户只有一个订单未写入数据库(系统下单逻辑涉及几十个函数,全是一些莫名奇妙的判断逻辑,混淆后可读性大幅提升的那种(当然是开玩笑的)),屎山作者已经跑路了,没人能看懂他代码,一个用户下多个订单数据会混乱。更牛逼的这套系统除了性能极差,运行 3 年没出错一次。一次调用 API 只能下一单,客户端随硬件交付,已经写死了,不能更新,然后客户端一次多少个订单就多少并发调用 API 提交,没有队列功能。目前要求 500 订单 10 秒内全部下单完成返回订单号(单独提交的话每个订单 0.01 秒左右能写入完数据拿到订单号)。
预分配订单号行不通,不运行一遍这部分屎山代码不能确定这个订单能不能提交,返回订单号就代表这个订单提交成功了,不能取消。目前打算改造成 Sub/Pub ,不知道能不能提升性能,或者 V 友有没有更好的改造方案?只要能让这屎山跑起来就行,代码多脏都没关系,改动需要尽可能小,不能把系统改炸。目前加硬件到 256GB 内存都没解决。
]]>PersonInfo person_info; person_info.age = 20; strcpy(person_info.addr, "sz");
redisContext * cc;
std::string name = "JACK";
如果要将上面的 name 和 person_info 传给 lua 脚本,应该怎么做呢? 下面的写法是不完整的, 但是又不知道该怎么写才对.
redisCommand(cc, "EVALSHA %s 1 %s", "redis 返回的 lua 脚本 sha1 值", name.c_str());
]]>而且还有个好处就是避免了常说的“缓存一致性问题”😄。
各位大佬厂里有没有这样用的?如果没有原因是什么呢?
]]> ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost"); Stopwatch stopwatch = Stopwatch.StartNew(); var count = 100000; for (int i = 0; i < count; i++) { var id = (int)redis.GetDatabase().StringIncrement("PMDCS:id_hello"); } stopwatch.Stop(); Console.WriteLine("用时:" + stopwatch.ElapsedMilliseconds + ", tps=" + (count / stopwatch.ElapsedMilliseconds));
测试结果 用时:8357, tps=11
]]>唯一的一次,还是自己 py 程序的问题,原因记不清楚了,好像是自己往 redis 里面写日志,忘了设置过期时间了.
还是就是 celery 用 redis 做 broker,会断联的问题,但是这个 python 单线程遇到 pub/sub 导致的,redis 还是稳
]]>https://github.com/hcymysql/redis_monitor
Redis Monitor 可以监控单机模式,哨兵模式,集群模式,并且录入一个主库或者从库 IP ,自动发现主库或者从库 IP 信息,无需人工再次录入。
采用远程连接方式获取数据,所以无需要在 Redis 服务器端部署相关 agent 或计划任务,可实现微信和邮件报警。
注:监控环境为 Redis 6.2 以上版本。
]]>然后主节点自动都转移到了其中一台机器上,此时 kill 掉这台机器,发现集群无法恢复,貌似是没法投票了。
应该如何避免这种情况发生呢?
]]>现在偶尔会出现一个问题:执行第 2 步操作时,有时候校验 ticket 会不通过
排查一下: redis 主从偏移有点大,造成主从数据不一样,校验接口走从库时查询不到 ticket 所以校验失败,大概是网络原因?
想到的一个解决方案是,校验接口强制走主库,但是哨兵模式下主库是不固定,有啥办法强制走主库吗( Java 语言的 SDK )?
或者有更好的解决方案?
]]>出现的情况是有时候 B 已经将数据写入 redis 了,但是 A 读不到. 查询写入使用的同一个 StringRedisTemplate.
有时候查不到就很迷
顺便吐槽下华为云 -- huaweicloud.csdn.net
点下阅读全文就自动获取手机号发送注册验证码..
Redis has a different evolution path in the key-value DBs where values can contain more complex data types, with atomic operations defined on those data types.
是不是所有 redis 7 的命令都是原子的?这跟 chatgpt 中给的答案不一样
]]>cacheKey + "_id"
,value 是随机数,expireTime=10 秒,若 key 已存在则失败),若生成 leaseId 失败则等待并重试。cacheKey + "_id"
、value=leaseId 的值)cacheKey + "_id"
的值)这样有什么问题吗?
]]>因为用户的客户端不可信,有几个问题不知道如何解决,独立的临时账号解决了客户端的安全问题,那么主要是客户端的资源占用的限制上。
暂时只想到这些,恳求各位赐教!
]]>not in the same slot
的错误,感觉我的这个脚本可能与会遇到。如果我真的通过 key 加前缀的方式保证他们在同一个 slot 内是不是也会导致 cluster 处于一个不健康的集群状态。 local userAttributiOnKey= KEYS[1]; local humeSource = KEYS[2]; local attributeKeys = cjson.decode(KEYS[3]); if redis.call("EXISTS", userAttributionKey) == 1 then -- get attribution cache local res = redis.call("GET", userAttributionKey); if res then return { 1, res }; end end local matchResult = {}; local hits = {}; local latestTime = 0; for i = 1, #attributeKeys do local indexInfoString = redis.call("GET", attributeKeys[i]); if indexInfoString then local indexInfo = cjson.decode(indexInfoString); local reportSource = indexInfo['report_source']; local uniqId = indexInfo['uniq_id']; local timestamp = tonumber(indexInfo['timestamp']); if humeSource == '' or humeSource == indexInfo['report_source'] then -- check attribute index info if reportSource ~= '' and uniqId ~= '' then if timestamp > latestTime then indexInfo['hit'] = attributeKeys[i]; indexInfo['match_res'] = 'matched'; matchResult = indexInfo; latestTime = timestamp; end table.insert(hits, attributeKeys[i]); else return redis.error_reply("Invalid Info:" .. indexInfoString); end end -- delete attribute key cache redis.call("DEL", attributeKeys[i]) end end if #hits > 0 then matchResult['hits'] = hits; local matchResultStr = cjson.encode(matchResult); -- set attribution cache redis.call("SET", userAttributionKey, matchResultStr, "EX", ARGV[1]); return { 1, matchResultStr }; end return { 0, cjson.encode(matchResult) };
请教下大佬和前辈,我这是不是一种错误的使用方式?以及 lua script 使用时有什么注意事项?
]]>redis-cli -a myPassword -h redis-host -p 6379 --scan --pattern '*myKey*'
但使用下面的命令,却无法删除成功
redis-cli -a myPassword -h redis-host -p 6379 --scan --pattern '*myKey*' | xargs redis-cli -a myPassword -h redis-host -p 6379 unlink (integer) 0
当然了, 把 unlink
改成 del
也是一样的效果, 不知道大家有没有解决过一样的问题?
报的异常如下
生产者异常: :"sendQueueMessageInfo leftPushAll : 异常 : org.springframework.data.redis.RedisConnectionFailureException: Unexpected end of stream.; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream. org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:67) org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:41) org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) org.springframework.data.redis.connection.jedis.JedisConnection.convertJedisAccessException(JedisConnection.java:142) org.springframework.data.redis.connection.jedis.JedisListCommands.convertJedisAccessException(JedisListCommands.java:486) org.springframework.data.redis.connection.jedis.JedisListCommands.lPush(JedisListCommands.java:84) org.springframework.data.redis.connection.DefaultedRedisConnection.lPush(DefaultedRedisConnection.java:444) org.springframework.data.redis.core.DefaultListOperations.lambda$leftPushAll$2(DefaultListOperations.java:124) org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224) org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184) org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95) org.springframework.data.redis.core.DefaultListOperations.leftPushAll(DefaultListOperations.java:124) com.service.ds.exec.DataSourceActuator.sendQueueMessageInfo(DataSourceActuator.java:392) com.service.ds.exec.DataSourceActuator.execAndPushQueue(DataSourceActuator.java:214) com.controller.rest.etl.EtlTaskRestController$1.run(EtlTaskRestController.java:86) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) java.lang.Thread.run(Thread.java:745)\
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream. redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:199) redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) redis.clients.jedis.Protocol.process(Protocol.java:153) redis.clients.jedis.Protocol.read(Protocol.java:218) redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:341) redis.clients.jedis.Connection.getIntegerReply(Connection.java:266) redis.clients.jedis.BinaryJedis.lpush(BinaryJedis.java:1119) org.springframework.data.redis.connection.jedis.JedisListCommands.lPush(JedisListCommands.java:82)\n\t... 12 more\n","stackTrace":"","reqId":"","elapsedTime":null}
消费者异常: 1","className":"com.service.schedule.SchedulerTask$1","lineNumber":"63","message":"队列消费 : 异常 : org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:281) org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:464) org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132) org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:95) org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:82) org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:211) org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184) org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95) org.springframework.data.redis.core.DefaultListOperations.leftPush(DefaultListOperations.java:99) com.service.etl.consumer.QueueConsumer.retryQueueMessage(QueueConsumer.java:101) com.service.etl.consumer.QueueConsumer.consume(QueueConsumer.java:180) com.service.schedule.SchedulerTask$1.run(SchedulerTask.java:60) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)java.lang.Thread.run(Thread.java:745)\nCaused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the poolredis.clients.util.Pool.getResource(Pool.java:53)redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)redis.clients.jedis.JedisPool.getResource(JedisPool.java:16) org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:271)\n\t... 14 more\nCaused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Connection resetredis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:202) redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) redis.clients.jedis.Protocol.process(Protocol.java:153) redis.clients.jedis.Protocol.read(Protocol.java:218) redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:341) redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:240) redis.clients.jedis.BinaryJedis.auth(BinaryJedis.java:2223) redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:108) org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:889) org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:424) org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:349) redis.clients.util.Pool.getResource(Pool.java:49)\n\t... 17 more\nCaused by: java.net.SocketException: Connection reset java.net.SocketInputStream.read(SocketInputStream.java:209) java.net.SocketInputStream.read(SocketInputStream.java:141) java.net.SocketInputStream.read(SocketInputStream.java:127) redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:196)\n\t... 28 more\n","stackTrace":"","reqId":"","elapsedTime":null}
redis 配置
public RedisTemplate<String, QueueMessageInfo> redisQueueMessageTemplate(RedisConnectionFactory factory) { RedisTemplate<String, QueueMessageInfo> template = new RedisTemplate<String, QueueMessageInfo>(); template.setConnectionFactory(factory); // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(om); template.setValueSerializer(serializer); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } @Bean public RedisConnectionFactory connectionFactory() { JedisPoolConfig poolCOnfig= new JedisPoolConfig(); poolConfig.setMaxTotal(maxActive); poolConfig.setMaxIdle(maxIdle); poolConfig.setMaxWaitMillis(maxWait); poolConfig.setMinIdle(minIdle); poolConfig.setTestOnBorrow(true); poolConfig.setTestOnReturn(false); poolConfig.setTestWhileIdle(true); JedisClientConfiguration jedisClientCOnfiguration= null; if (ssl) { jedisClientCOnfiguration= JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and() .readTimeout(Duration.ofMillis(timeout)).useSsl().build(); } else { jedisClientCOnfiguration= JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and() .readTimeout(Duration.ofMillis(timeout)).build(); } RedisStandaloneConfiguration redisStandalOneConfiguration= new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setDatabase(redisDatabase); redisStandaloneConfiguration.setPort(port); redisStandaloneConfiguration.setPassword(password); redisStandaloneConfiguration.setHostName(host); RedisConnectionFactory redisCOnnectionFactory= new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); logger.info("初始化 redis 链接池 : JedisPoolConfig"); return redisConnectionFactory; } # 池在给定时间可以分配的最大连接数。使用负值无限制。 #spring.redis.pool.max-active=200 spring.redis.pool.max-active=200 # 池中“空闲”连接的最大数量。使用负值表示无限数量的空闲连接。 # spring.redis.pool.max-idle=20 spring.redis.pool.max-idle=20 # 连接分配在池被耗尽时抛出异常之前应该阻塞的最长时间量(以毫秒为单位)。使用负值可以无限期地阻止。 spring.redis.pool.max-wait=9000 # 目标为保持在池中的最小空闲连接数。这个设置只有在正面的情况下才有效果。 #spring.redis.pool.min-idle=20 spring.redis.pool.min-idle=20
]]>