1.为什么要用redis做缓存?
-
减少数据库压力:在高流量的外卖平台中,数据库可能会面临巨大的查询压力。通过使用Redis缓存热点数据,可以减少对数据库的直接访问,从而降低数据库的压力。
-
提高响应速度:用户在浏览商家、菜品和下单时,如果每次都需要从数据库读取数据,会严重影响用户体验。使用Redis缓存这些数据,可以显著提高响应速度。
-
处理高并发 :在高峰时段,外卖平台可能会遇到高并发访问。Redis能够处理大量的并发读写操作,保证系统稳定运行。(存储在内存,单线程(单个命令的执行非常快速,操作是原子的,不需要担心多线程并发问题),Redis使用非阻塞I/O多路复用模型,可以同时处理多个客户端请求,这是通过多路复用技术实现的。它允许Redis使用单个线程来监听多个文件描述符,从而处理成千上万的并发连接)
2.redis是单线程还是多线程?
Redis在大多数情况下被认为是单线程的。这是指Redis的核心网络I/O和键值对操作是由单个线程处理的。这个单线程模型使得Redis内部非常简单,避免了上下文切换和竞态条件,这也是Redis能够提供高性能服务的一个关键原因。
然而,需要注意的是,Redis的单线程特性并不意味着它不能利用多核CPU。实际上,Redis可以通过以下方式间接利用多核:
-
多实例部署:可以在同一台服务器上运行多个Redis实例,每个实例绑定到不同的CPU核心上,以此来利用多核。
-
后台线程:虽然Redis的主要操作是单线程的,但它确实使用了一些后台线程来处理一些特定的任务,例如:
- 数据持久化(将内存中的数据保存到磁盘上。RDB【当执行
BGSAVE
时,Redis的主线程会fork一个子进程,主线程可以继续处理客户端请求,而子进程则在后台执行写操作。】和AOF【写命令执行后,会被追加到AOF缓冲区。根据配置,Redis可能会使用一个或多个后台线程来将AOF缓冲区的内容写入到AOF文件,并且同步到磁盘。】)。 - 删除对象(当内存不足时,Redis可能会使用后台线程来执行对象的删除操作LRU【当内存使用达到
maxmemory
限制时,Redis开始执行淘汰策略。对于大型对象,Redis可能会使用后台线程来异步执行删除操作,以避免阻塞主线程。】)。
- 数据持久化(将内存中的数据保存到磁盘上。RDB【当执行
从Redis 6.0开始,Redis引入了多线程功能,但这主要是指对于某些特定操作,如处理网络I/O,可以使用多个线程来提高性能。键值对操作本身仍然是单线程的,这保证了操作的原子性和一致性。多线程I/O可以使得Redis在处理大量并发连接时,能够更好地利用CPU资源,尤其是在网络带宽成为瓶颈时。
3.redis采用单线程的原因?
Redis采用单线程模型的原因主要包括以下几点:
- 简洁性:
单线程模型简化了内部复杂性和上下文切换开销。在单线程环境中,不需要考虑锁的问题,也没有线程同步的问题,因此代码更简洁,更容易维护。
- 性能:
Redis的单线程模型由于其内存操作和优化的数据结构,能够提供非常高的性能。对于大多数用途而言,单线程已经足够快。
单线程避免了多线程的上下文切换和竞态条件,这些在多线程环境中可能会成为性能瓶颈。
3.内存利用:
由于不需要考虑线程安全问题,Redis可以更有效地利用内存,因为它不需要为每个线程分配独立的内存空间或者为数据结构添加额外的锁。
4.多核CPU的利用:
虽然Redis是单线程的,但它可以通过运行多个实例来利用多核CPU的优势。每个实例可以绑定到一个CPU核心上,从而实现并行处理。
5.避免复杂的多线程问题:
多线程编程可能会引入死锁、竞态条件、线程安全等问题,这些问题可能会很难调试和修复。单线程模型避免了这些问题。
6.内部优化:
Redis内部很多操作都是经过优化的,比如非阻塞I/O等,这些优化使得单线程能够高效地处理大量请求。
7.专注核心功能:
单线程模型使得Redis的开发者可以专注于核心功能的优化,而不是花费大量时间处理多线程相关的复杂问题。
4.缓存套餐导致value特别大,如何优化?
只缓存套餐重要数据,菜品详细数据不存
5.如何缓存热点数据,redis缓存淘汰策略?
提供一种简单实现缓存失效的思路: LRU(最近少用的淘汰)
即redis的缓存每命中一次,就给命中的缓存增加一定ttl (过期时间)(根据具体情况来设定, 比如10分钟).一段时间后, 热数据的ttl都会较大, 不会自动失效, 而冷数据基本上过了设定的ttl就马上失效了.
缓存淘汰策略
-
默认策略(allkeys-lru) :当内存使用达到
maxmemory
配置的限制时,Redis会淘汰最长时间未被访问的键(LRU,最近最少使用)。 -
volatile-lru:仅淘汰设置了过期时间的键中最近最少使用的键。
-
volatile-ttl:淘汰那些即将过期的键。
-
volatile-random:随机淘汰设置了过期时间的键。
-
allkeys-random:随机淘汰任何键。
-
noeviction:不删除任何键,当内存达到限制时,返回错误。
-
allkeys-lfu:淘汰最不经常使用的键(LFU,最不经常使用)。
6.如何实现LRU(最近最少使用)
双向链表【维持顺序】+hashmap【快速查找】(头新尾旧)
- 每个数据项都包含两部分:键(Key)和值(Value),以及一个时间戳(Timestamp),用于记录数据项的最后访问时间。
-
访问操作:
- 当数据项被访问时,更新其时间戳为当前时间。
- 如果数据项已经存在于缓存中,将其从当前位置移动到链表的头部;如果数据项不在缓存中,则将其添加到链表的头部。
-
淘汰操作:
- 当缓存达到其容量限制时,淘汰链表尾部(即最长时间未被访问的)的数据项。
- 如果需要淘汰的数据项位于链表头部,则将其移动到尾部,然后淘汰尾部数据项。
7.websocket消息推送,具体怎么实现,服务器什么时候知道可以推送?
支付成功之后,有一个支付回调的接口(PayNotifyController),里面有一个支付成功提醒的方法,调用了orderServicede的paySuccess(),进行订单状态修改(已支付)以及来单提醒(使用websocket)
java
@Autowired
private WebSocketServer webSocketServer;
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
public void paySuccess(String outTradeNo) {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
// 根据订单号查询当前用户的订单
Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
//
Map map = new HashMap();
map.put("type", 1);//消息类型,1表示来单提醒
map.put("orderId", orders.getId());
map.put("content", "订单号:" + outTradeNo);
//通过WebSocket实现来单提醒,向客户端浏览器推送消息
webSocketServer.sendToAllClient(JSON.toJSONString(map));
///
}
8.websocket断开,如何推送未接单订单?
设定推送时间以及状态,推送当前时间之前的状态为未接单的订单