高并发基础-一文带你了解Redis及其常见用法与应用场景
mhr18 2024-10-23 11:32 21 浏览 0 评论
1. 概述
Redis是一个键值对存储的存储系统;一般用做缓存比较多,也可以将其作为数据库及消息中间件使用。
传统应用中,数据存储在关系型数据库中,前端请求到来时通过SQL语句查询关系型数据库中的数据并返回给前端;然而关系型数据库中的数据都是存储在文件系统中,读取数据涉及到多次对文件系统的调用,效率受到限制。一般情况下我们可以使用基于内存的缓存来对查询活动进行提速;而基于内存的的缓存又分成两种:
- 基于应用内存的缓存
- 分布式缓存
比如,我们自己在应用里实现一个本地缓存,使用一个Map存储数据,当来请求时先从Map里面根据某个关键字查找,有就直接返回前台,没有再查数据库,然后将查询到的结果存储到Map后再返回给前台。
但这种缓存有一个限制,就是这个缓存只是单点的,如果想要让缓存失效需要通过一些其它补偿机制进行处理;当然单点不存在任何问题;多点部署的情况下,比如我们通过其中一个节点修改了数据库中的数据,这个节点也刷新了他的本地缓存,但另外一个节点并不知道这个数据被修改了,他的缓存不会被刷新,从而造成数据不一致。这在分布式系统中是绝对不允许的。
这个时候我们就将这个缓存的功能抽取出来,单独做成一个系统,所有应用操作缓存的时候都调用这个单独系统的接口。因为缓存都在单系统里面,缓存的失效、刷新等就不会存在不一致的问题了。这种方式就是分布式缓存,你可以自己实现,也可以使用Redis或者其它的内存数据库。当然要自己实现考虑的东西太多了,一般应该很少有人会去这么做。
Redis具有以下特性:
- 基于键值对存储,类似Java中的Map;
- 支持数据持久化;
- 支持集群部署及主从备份;
- 性能极高;
- 保证操作原子性;
- 可以指定数据的过期时间;
- ...
Redis的应用场景包括:
- 缓存:这是Redis用的最广泛的场景了,相对于关系型数据库来说,引入Redis做缓存能够极大的提升系统的并发能力。
- 分布式锁
- 限流:如实现令牌桶算法等;
- 计数器
- 排行榜,如按一定的规则统计排前10的热点事件等;
- ...
2. Redis数据类型
前面说过,Redis以键值对存储,Key一般为String(实际上任何二进制序列都可以做Key,一般都会转成字节流)。
而Value则支持多种,通过不同Value类型,Redis为各种应用场景提供了支持:
- 单值:实际上可以使用序列化后的字节流来存储,因此Value可以是任何对象,不过在存储时需要将其序列化成字节流,而读取时需要反序列化为相应的对象;Spring中一般使用JSON的方式来进行序列化与反序列化,也可以使用JDK本身的序列化方式。
- 列表(Lists):按插入顺序排序的元素集合;
- 集合(Sets):无序且不重复的元素集合;
- 有序集合(Sorted Sets):有序而不重复的元素集合,每个元素关联一个分数(Score),元素通过分数进行排序;
- 哈希(Hashes):存储键值对集合;类似于Map中存Map;
- 位图(Bit Arrays):如将String的值当作一系列的位进行存储,可以设置和清除单独的位,列出所有为1的位或者查找第一个值为1的位等;
- HyperLogLogs: 快速计算不同元素数量的一种数据结构; (类似于数据库中的count(distinct));具有速度快消耗空间小等优点;但计算结果不是精确的,有一定的误差;
3. 数据类型详解
3.1 单值
对于单值对象的操作包含有以下几种命令:
3.1.1 基本命令
- set
设置key对应的值
127.0.0.1:6379> set a b
OK
- get
获取Key对应的值
127.0.0.1:6379> get a
"b"
- del
删除Key对应的值
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> get a
(nil)
3.1.2 带冲突检测
- setnx
设置Key对应的Value,如果Key已经存在,设置失败;如:
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set a a nx
(nil)
可以使用setnx来做分布式锁;
- setxx
与setnx相反,设置key对应的Value,如果key存在,设置成功,反之则设置失败
127.0.0.1:6379> set b b xx
(nil)
127.0.0.1:6379> set b b
OK
127.0.0.1:6379> set b c xx
OK
127.0.0.1:6379> get b
"c"
3.1.3 自增
- incr
对数值型的Value进行自增,并返回新值,如果Key对应的Value不是数值型,会报错;如:
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> incr a
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> incr a
(integer) 2
当key对应的值不存在时,执行incr后其值会变成1,如下所示:
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> incr a
(integer) 1
一般使用incr来做计数;
- incrby
将数值型的Value增加一个指定的值,并返回增加后的值 ,如
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> incrby a 3
(integer) 4
3.1.4 过期设置
过期时间设置在做缓存的时候非常有用,一定用做缓存时除非特殊情况最好是设定数据的过期时间,以避免Redis中存储过多数据导致性能下降。
- expire
指定数据的过期时间,如:
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> expire a 2
(integer) 1
127.0.0.1:6379> get a
(nil)
- set ex
在赋值的同时指定过期时间,如:
127.0.0.1:6379> set a 2 EX 3
OK
127.0.0.1:6379> get a
"2"
127.0.0.1:6379> get a
(nil)
3.1.5 其它
- getset
赋值并返回老的值,如:
127.0.0.1:6379> getset a 2
(nil)
127.0.0.1:6379> getset a 3
"2"
- mset /mget
批量赋值与取值
127.0.0.1:6379> mset a 1 b 2
OK
127.0.0.1:6379> mget a b
1) "1"
2) "2"
3.2 列表
Redis中的列表基于Linked List实现,因此添加新元素的效率很高,但相应的,使用下标访问元素的性能相对较低。如果顺序访问的场景过多,使用列表不是很合适,可以使用下方将会提到的有序集合。列表是按插入顺序排序的,作用上类似于常规的队列。
列表常用操作包含以下几个:
- lpush/rpush:从头部或尾部插入元素;
- lrange:获取列表中指定范围的元素列表;
- rpop/lpop:从头部或尾部获取并删除元素;
- brpop/blpop:阻塞式获取元素;如果队列放置空时会将对应的消费者阻塞,直到有生产者往其中插入数据; 等待的消费者会按FIFO的顺序排队,同时也可以指定消费者阻塞的时间。
列表可用于实现聊天系统,也可以作为不同进程间传递消息的队列; 或者存储需要按顺序访问的其它数据,如一篇文章的评论列表等;
3.3 无序集合
Set是无序且不重复的集合;主要包含以下几种操作:
- sadd:添加元素到集合中
- spop: 随机获取并删除一个元素
- sismember: 判断元素是否在集合中存在
- sinter:取两个集合的交集
- sunionstore:取两个集合的并集
无序集合最典型的应用场景就是实现对象标签;如给id为1000的文章打上1、4、5、6这四个标签,可以这样:
sadd article:1000:tags 1 4 5 6
有时候可能会要相反的结果,如打上1标签的文章列表:
sadd tag:1:article 1000
sadd tag:1:article 1001
然后我们要根据多个标签查询文章列表就会很简单,将这几个标签中存储的集合做交集:
sinter tag:1:article tag:2:article ...
还有一种应用场景就是粉丝,每个账号的粉丝存储一个列表,每个人粉的用户存一个列表,这样就可以利用这两类列表快速完成很多种查询,如找两个人共同粉的明星、找两个明星共同的粉丝等;
3.4 有序集合
有序集合是在集合的基础上保持元素有序性。每个元素在添加时可以指定一个分值(Score),Redis依据这个分值来保持集合中的元素的顺序。
常用操作:
- zadd:添加元素到有序集合中,与sadd不同的是它需要指定一个分值,如:
127.0.0.1:6379> zadd test 2 a
(integer) 1
意思是将元素a添加到集合test中,并且指定其分值为2; 也可以通过incr的方式增加其分值,如:
127.0.0.1:6379> zadd test incr 2 a
"4"
- zrange: 获取集合中的元素,需要指定两个下标;如我们继续往集合中添加元素,然后使用zrange获取元素:
127.0.0.1:6379> zadd test 2 a
(integer) 1
127.0.0.1:6379> zadd test 3 b
(integer) 1
127.0.0.1:6379> zadd test 1 c
(integer) 1
127.0.0.1:6379> zadd test 4 d
(integer) 1
127.0.0.1:6379> zrange test 0 -1
1) "c"
2) "a"
3) "b"
4) "d"
可以看出再来的列表是排好序的;(c=1/a=2/b=3/d=4)其中-1表示的是到最后一个元素;
如果我们要获取Top2的元素,那么可以:
127.0.0.1:6379> zrange test 0 1
1) "c"
2) "a"
- zrevrange: 按逆序获取元素列表;如:
127.0.0.1:6379> zrevrange test 0 1
1) "d"
2) "b"
- withscrores:如果想要查询结果将分值与查询出来,可以添加这个参数,如:
127.0.0.1:6379> zrevrange test 0 1 withscores
1) "d"
2) "4"
3) "b"
4) "3"
- zrangebyscore 根据指定的范围获取数据,如(inf表示infinite,即无穷大,-inf是无穷小)
127.0.0.1:6379> zrangebyscore test -inf 2
1) "c"
2) "a"
127.0.0.1:6379> zrangebyscore test -inf 3
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> zrangebyscore test 1 2
1) "c"
2) "a"
- zremrangebyscore 根据指定的范围删除元素
- zrank/zrevrank获取某个元素在列表中的排序序号,如(从0开始):
127.0.0.1:6379> zrank test a
(integer) 1
127.0.0.1:6379> zrank test b
(integer) 2
127.0.0.1:6379> zrank test c
(integer) 0
有序集合最大的特点就是为集合中的每个元素指定一个分值,因此在需要获取Top N的场景下非常有用。
3.5 哈希
哈希使用键值对的方式来存储元素,如:
127.0.0.1:6379> del test
(integer) 1
127.0.0.1:6379> hset test a b
(integer) 1
127.0.0.1:6379> hget test a
"b"
主要有以下几个方法:
- hget/hmget:赋值
- hset/hmset :获取值
- hincrby:为某个元素增加其值,如:
127.0.0.1:6379> hset test c 1
(integer) 1
127.0.0.1:6379> hincrby test c 20
(integer) 21
哈希可以用于存储对象属性,能够非常快速地通过属性的名称查询到其值。
3.5 位图
位图其实就是一长串的0/1串;
它主要包含以下几种操作:
- setbit设置指定位置的位值
127.0.0.1:6379> setbit test 0 1
(integer) 0
127.0.0.1:6379> setbit test 3 1
(integer) 0
- getbit获取指定位置的位值
127.0.0.1:6379> getbit test 3
(integer) 1
- bitcount 计算位值为1的所有位置数量:
127.0.0.1:6379> bitcount test
(integer) 2
- bitpos获取值为指定值的第一个元素所在的下标:
127.0.0.1:6379> bitpos test 0
(integer) 1
127.0.0.1:6379> bitpos test 1
(integer) 0
- bitop对多个集合进行操作,将结果存储到另外一个集合中,如:
127.0.0.1:6379> bitop and result test test1
(integer) 1
意思是对test/test1两个集合求交集,将其存储到result中。
位图的常见使用场景,如我们给将每个用户每天的登录情况存储成一个位图,如果某一天有登录,那么将这一天对应位置设置成1;无登录则不需要设置(默认为0);
存储后我们可以方便地对多个用户的集合进行各种与、或等操作,多个用户共同登录的天数:
127.0.0.1:6379> setbit a 0 1
(integer) 0
127.0.0.1:6379> setbit a 3 1
(integer) 1
127.0.0.1:6379> setbit b 2 1
(integer) 1
127.0.0.1:6379> setbit b 3 1
(integer) 1
127.0.0.1:6379> setbit c 0 1
(integer) 0
127.0.0.1:6379> setbit c 3 1
(integer) 0
127.0.0.1:6379> setbit c 1 1
(integer) 0
127.0.0.1:6379> bitop and result a b c
(integer) 1
使用位图最大的好处是使用尽量少的存储空间获得尽量高的计算效率。
3.6 HyperLogLogs
主要用做计算不同元素的数量,有点像set,但他并不实际的存储元素,只是根据添加的元素,使用一定的算法估算出不同元素数量,因此不需要消耗很大的存储空间。一定要注意他只是估算,不能保证100%精确,因此并不适用于对精确度要求很高的场景;
它主要包括以下操作:
- pfadd添加要进行计算的元素到命令中,注意它并不会真的存储这个元素;
- pfcount计算集合中的不同元素数量;
如:
127.0.0.1:6379> del test
(integer) 1
127.0.0.1:6379> pfadd test a b c d a b
(integer) 1
127.0.0.1:6379> pfcount test
(integer) 4
127.0.0.1:6379> pfadd test e
(integer) 1
127.0.0.1:6379> pfcount test
(integer) 5
具体算法比较复杂,感兴趣的可以自己去找一下。
4. 高可用
Redis可以通过主从配置、集群部署、哨兵等方式来实现高可用。
我们可以通过redis-trib工具将多个单独运行的Redis Server组合成集群,同时可以指定每个主节点对应的从节点数量;最终创建好的集群中包含有多个主节点,每个主节点有其对应的从节点。
当我们要存储数据时,Redis会根据基于哈希槽的数据分片算法(一致性哈希),将数据分配到某个主节点上进行存储,主节点的数据会同步到其对应的从节点上,以此来保证数据安全性(但由于同步延迟,并不能保证强一致性,如果某个主节点挂掉可能会有数据丢失的风险)。
那么在主节点挂掉时如何保证从节点能够顺利切换成主节点呢?
Redis通过哨兵(Sentinel)来实现;由一个或多个Sentinel实际缓存的Sentinel系统可以监视任意多个主服务器,以下这些主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动选择从服务器将其升级成主服务器;
Sentinel执行以下三个任务:
- 监控: 不断检查主服务器与从服务器是否运行正常
- 提醒:当被监控的某个服务器出现问题时,可以通过API向管理员或者其它应用程序发送通知;
- 自动故障迁移:当一个主服务器不能正常工作时,会开始一次自动故障迁移,将失效的主服务器其中的一个从服务器升级为新的主服务器,并让失效主服务器的其它从服务器改为复制新的主服务器; 当客户端试图连接老的主服务器时,集群也会向客户端返回新主服务器的地址。
Redis sentinel只是一个运行在特殊模式下的Redis服务器,可以在启动一个普通Redis服务器时通过指定-sentinel选项来启动Redis Sentinel
启动完Sentinel后,如Spring Boot等客户端就可以连接到Sentinel服务器,从其中获取实际Redis服务器地址并进行操作;同时它会启动监听来监听哨兵的主从服务器变更消息,从而在主从发生切换时能够及时响应其变化。
Sentinel也可以做集群部署,一般最少部署三个来做到高可用,因为涉及到Sentinel对于主节点下线的一个投票处理,两个的时候无法正常运行。
以三个哨兵、两个主从集群配置的高可用下Redis的部署图如下所示:
其中哨兵与哨兵之间、哨兵与任意的主节点、任意的从节点都会进行心跳检测,以便在某个节点出现异常时能够尽快对其响应。
5. 其它应用
5.1 管道
常规方式下,每一次Redis的操作都需要等待操作返回再执行下一次操作,如果我们有一批操作需要同时执行,这种效率是很低的。
而使用管道后,在管道中执行命令中不需要等待命令返回就可以继续执行下一条命令,效率会得到较大的提升。
Spring Boot中可以通过RedisTemplate的executePipelined的方式来执行管道命令。如:
public void executePipelined(Map<String, String> map, long seconds) {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
redisTemplate.executePipelined(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
map.forEach((key, value) -> {
connection.set(serializer.serialize(key), serializer.serialize(value),Expiration.seconds(seconds), RedisStringCommands.SetOption.UPSERT);
});
return null;
}
},serializer);
}
与管道相类似的有一个执行脚本的功能。上面说到在管道中执行下一条命令时不需要等待上一条命令执行完毕,因此,如果我们批量执行的命令需要保持顺序,就不能使用管道了。这个时候就可以使用脚本来执行。 脚本类似数据库中的存储过程,可以将一系列操作封装成一个原子操作,客户端在调用的时候传入脚本内容及参数给Redis,Redis服务器将会顺序执行脚本中的内容,同时保证整个脚本执行的原子性。由于脚本的执行是在服务器上,后面的脚本即使依赖于前面脚本的执行结果,也与客户端之间不存在数据的交互,因此其效率比不使用脚本的方式要高。
5.2 发布/订阅
类似常规的MQ,Redis也支持发布订阅的模式。通过subscribe/publish等命令来进行订阅与发布。
5.3 事务
Redis通过multi/exec/discard等命令支持事务,从而使得客户端的一系列操作能够作为一个原子性操作被执行;
如:
SessionCallback<Object> callback = new SessionCallback<>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("name", "1");
operations.opsForValue().set("gender", "男");
operations.opsForValue().set("age", "30");
return operations.exec();
}
};
stringRedisTemplate.execute(callback)
需要注意的是,Redis事务并不支持回滚,即使事务中的某条命令执行失败了,Redis仍旧会执行余下的任务。但在执行exec之前,我们可以通过discard来手动放弃事务。
我们也可以通过watch命令在事务处理过程中做监控,它能够在发现某个被监听的对象被其它连接修改后,将当前的事务置成失败。我们可以在应用程序中,检测到这类失败时就再重新尝试,或者直接中止执行。
从定义上来说,Redis中的脚本本身就是一种事务,所有可以在事务中处理的事情,都可以通过脚本来完成。而且脚本一般来说执行速度要更快一些;那为什么会有两种?主要是因为脚本是后引入的,而事务是比较早就已经支持的特性。
因此应用程序中完全可以使用脚本来替代需要使用事务的地方。
5.4 LRU
当Redis被当做缓存使用时,我们新增数据,它能够自动地回收旧数据,这对于应用程序来说会非常有用,否则可能会造成缓存数据量越来越多,Redis性能下降越来越厉害。
Redis通过近似的LRU算法来实现内存回收。
可以通过maxmemory-policy来配置内存达到限制时的策略:
- noeviction: 执行那些使得内存增加的命令时报错;
- allkeys-lru: 尝试回收最少使用的键;
- volatile-lru: 尝试回收最少使用的键,但仅限于已过期的;
- allkeys-random: 回收随机的键;
- volatile-random: 回收随机的过期键;
- volatile-ttl: 回收在过期集合的键,并且优选回收存活时间较短的键;
如果没有键满足回收的前提条件,volatile-lru/volatile-random/volatile-ttl与noevication结果是一样的;
5.5 分布式锁
主要是通过setnx命令,即设置某个对象值,如果失败则说明其它连接给他赋值了,表示锁被占用,这个时候可以循环执行等待,也可以使用发布、订阅方式的等通知;
需要注意的是锁超时、锁释放、锁续约等处理;
一个简单的实现如下:
/**
* 通过分布式锁执行任务
* 最多支持60秒内的任务,如果超过60秒未执行完成,锁会超期
*
* @param lockKey 分布式锁存储在Redis中的Key值
* @param task 需要执行的任务
*/
public void callWithLock(String lockKey, Runnable task) {
String finalKey = KEY_PREFIX + "lock:" + lockKey;
String value = UUID.randomUUID().toString();
int tryLockCount = 0;
Boolean result = null;
while (tryLockCount < 10) {
result = redisTemplate.execute((RedisCallback<Boolean>) connection ->
connection.set(finalKey.getBytes(), value.getBytes(), Expiration.from(60, TimeUnit.SECONDS),
RedisStringCommands.SetOption.SET_IF_ABSENT));
if (null != result && result) {
break;
}
tryLockCount++;
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
if (null != result && result) {
try {
task.run();
} finally {
// 释放锁,需要判断value值与当前线程的Value值是否一致,不一致则表明已经切换了线程,不能释放
String cacheValue = redisTemplate.opsForValue().get(finalKey);
if (!StringUtils.isBlank(cacheValue) && cacheValue.equals(value)) {
redisTemplate.delete(finalKey);
}
}
} else {
throw BusinessException.create("加锁超时");
}
}
基于Redis实现的分布式锁比较出名的是RedLock,感兴趣的可以了解下。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)