外卖点餐系统05

外卖点餐系统05
Redis常用数据类型

Redis简介

Redis在MySQL基础上提高数据存取效率

Redis是一个基于内存的key-value结构数据库。
MySQL是基于磁盘实现的数据库,由于数据读写需求越来越大,这种基于磁盘的读取速度就不够了,于是是否能够像CPU一样为了提高速度,也搞一个类似内存的东西。于是Redis就产生了。
应用程序从MySQL查询到的数据在Redis中进行登记,后续再查询数据先搜索Redis,Redis没有再搜索MySQL。

Redis缓存内容设置超时时间

Redis缓存的数据都是在内存上,但是内存的空间资源很有限,所以需要给缓存内容设置一个超时时间,具体设置多久交给应用程序设置,Redis只需要在时间过期后及时删除数据

Redis删除策略

  • 定期删除,eg:每100ms随机清理部分缓存数据,这里不清理所有Redis内存过期数据,是因为全面扫描一遍需要时间过久
  • 但是由于随机算法,有可能使得某些键值长期不被随机到,未被清理
  • 触发删除,为了避免有些键值长期不被清理,于是有了出发删除
  • 主动发起请求检查key是否过期,如果过期立马删除,这种方式因为是被动方式出发,不查询就不会发生,所以也叫惰性删除
  • 但是仍有些键值,既逃脱了随机算法也没有被查询。
  • 内存淘汰策略——万一过期时间设置的很长,没有等到定时清理,内存就满了
  • allkeyS-LRU:使用LRU算法删除最近最少使用的键值
  • volatile-lru:使用LRU算法从设置了过期时间的键集合中删除最近最少使用的键值
  • ...

Redis缓存穿透

问题:有时候查询的数据MySQL不存在,redis中肯定也没有,MySQL也会执行一次空查询,Redis根本没起作用,这就是Redis穿透。
解决:布隆过滤器,擅长从超大的数据集中快速告诉你查找的数据不存在

Redis缓存雪崩

问题:一大批数据几乎同时过了有效期,又发生了很多对这些数据的请求
解决方法:把键值的过期时间进行随机,并设置热点数据永不过期

Redis总结

Redis是一个基于内存的key-value结构数据库。

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品、资讯、新闻)
  • 企业应用广泛

Redis常用数据类型介绍

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:

  • 字符串string:普通字符串,Redis种最简单的数据类型
  • 哈希hash:类似于Java中的HashMap结构,适合存储对象
  • 列表list:是一个简单的字符串列表,按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合set:string类型的无序集合,没有重复元素,类似于Java中的HashSet,可以进行集合运算,求交集,应用场景:朋友圈共同点赞朋友。
  • 有序集合 sorted set:string类型元素的有序集合,集合中每个元素关联一个分数(score),根据分数升序排列,没有重复元素,经典应用场景:排行榜
    Redis中value常用数据结构示意图.png

value-字符串string 操作命令

  • set key value 设置指定key的值
  • get key 获取指定key的值
  • setex key seconds value 设置指定的key值,并将key的过期时间设置为seconds秒,(Set with Expiry)典型应用场景:短信验证码
  • setnx key value 只在key不存在时设置key的值,应用场景:分布式锁

value-哈希hash

  • hset key field value 将哈希表key中的字段field的值设为value
  • hget key field 获取存储在哈希表中指定字段的值
  • hdel key field 删除存储在哈希表中的指定字段
  • hkeys key 获取哈希表中所有字段,返回所有的field
  • hvals key 获取哈希表中所有值,返回所有的value

列表list

  • lpush key value1 [value2] 将一个或多个值插入到列表头部,lpush中的l是left
  • lrange key start stop 获取列表指定范围内的元素
  • rpop 移除并获取列表最后一个元素
  • llen key 获取列表长度

集合set

  • sadd key member1 [member2] 向集合添加一个或多个成员,sadd key1 xiaoming xiaohong
  • smembers key 返回集合中所有成员
  • scard key 获取集合的成员数
  • sinter key1 [key2] 返回给定所有集合的交集
  • sunion key1 [key2] 返回所有给定集合的并集
  • srem key member1 [member2] 删除集合中一个或多个成员,rem代表remove

有序集合 sorted set

  • zadd key socre1 member [score2 member2] 向有序集合添加一个或多个成员
  • zrange key start stop [withscores] 通过索引区间返回有序集合中指定区间内的成员,增加withscores会在返回成员的同时返回分数, zrange key 0 -1(返回key所有成员)
  • zincrby key increment member 有序集合中对指定成员member的分数加上增量increment
  • zrem key member [member ...] 删除集合中的一个或多个成员

通用命令

Redis的通用命令不分数据类型,都可以使用的命令:

  • keys pattern 查找所有符合给定模式(pattern)的key (keys *)返回所有key
  • exists key 检查给定key是否存在
  • type key 返回key所存储的值的类型
  • del key 该命令用于在key存在时删除key

在Java中操作Redis

Spring Data Redis

Spring Data Redis的使用方式:

  1. 导入Spring Data Redis的maven坐标——在项目依赖文件pom.xml中添加相应依赖
<!-- sky-server的pom.xml文件 -->
<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

spring-boot-starter-data-redis 没有指定版本号。这可能是因为通过 Spring Boot 依赖管理,它会自动选择合适的 Redis 版本。如果你在父工程的 中定义了 spring-boot-starter-data-redis 的版本,那么子模块中的依赖会自动继承父工程定义的版本号。

  1. 配置Redis数据源
    配置文件中(application.yml)配置本地Redis端口号,用户名和密码
spring:
  redis:
    host: localhost
    port: 6379
    password:
    database: 
  1. 编写配置类,创建RedisTemplate对象


@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    // 依赖文件pom.xml中的,spring-boot-starter-data-redi会将连接工厂对象创建好,并放入spring容器当中
    // RedisConnectionFactory 是redis连接工厂对象,这里是通过spring Bean注解按照类型注入的
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 该方法返回redisTemplate对象,后续利用RedisTemplate对象操作Redis
        RedisTemplate redisTemplate = new RedisTemplate();
        // 让redisTemplate关联上redis连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

  1. 通过RedisTemplate对象操作Redis
@SpringBootTest
public class SpringDataRedisTest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testRedisTemplate() {
        // 使用redisTemplate操作redis数据对象
        // 对应redis五种数据类型,每个类型都封装了接口
        ValueOperations valueOperations = redisTemplate.opsForValue();
        HashOperations hashOperations = redisTemplate.opsForHash();
        ListOperations listOperations = redisTemplate.opsForList();
        SetOperations setOperations = redisTemplate.opsForSet();
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    }
    /**
     * 操作字符串类型的数据
     */
    @Test
    public void testString() {
        // set get setex setnx
        redisTemplate.opsForValue().set("city", "Beijing");     // set
        String city = (String) redisTemplate.opsForValue().get("city");     //get
        System.out.println(city);
        redisTemplate.opsForValue().set("city1", "Shanghai", 4, TimeUnit.MINUTES);   // setex

        redisTemplate.opsForValue().setIfAbsent("province", "Hubei");   // setnx
        redisTemplate.opsForValue().setIfAbsent("province", "Ningxia");     // setnx

    }
}
  1. 为什么 redisTemplate 是通过配置类调用的
    程序在 RedisConfiguration 中定义RedisTemplate Bean 并通过 @Bean 注解公开它时,Spring 自动将这个 Bean 加入到应用上下文。之后,无论是在应用主代码中还是在测试类中,只要你使用 @Autowired,Spring 都会自动注入相应的 Bean。
    换句话说,SpringDataRedisTest 类并不需要手动调用 RedisConfiguration。Spring Boot 会自动加载并初始化这个配置类中的所有 Bean。@Autowired 注解会让 Spring 自动处理依赖注入,不需要开发者手动调用配置类中的方法。

  2. Spring 容器如何管理 Bean?
    Spring 容器在启动时会扫描所有带有 @Configuration 和 @Bean 注解的类,并将它们注册为 Spring 管理的 Bean。这些 Bean 会被自动装配到需要它们的地方(通过 @Autowired)。因此,在测试类SpringDataRedisTest中,Spring 容器在测试类启动时已经准备好了 RedisTemplate,并通过 @Autowired 注入到相应的字段。

  3. 总结

  • 你在 RedisConfiguration 中定义了一个 RedisTemplate Bean。
  • Spring 容器会自动识别并加载这个 Bean,注册到应用上下文中。
  • 在测试类中,@Autowired 告诉 Spring 自动将 RedisTemplate 注入,无需手动调用配置类。

店铺营业状态设置

接口设计

店铺营业状态设置接口设计.png
店铺状态数据存储方式:基于Redis字符串数据存储
key: SHOP_STATUS value: 0 or 1
约定: 1表示营业,0表示打烊

代码开发

客户端商店相关接口开发

@RestController("adminShopController")      // 这里是自定义了该Bean的名称,因为controller.admin和controller.user下
                                            // 有同类名的ShopController,要将这两个类名在Spring Bean容器中区分开来
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
    @Autowired
    private RedisTemplate redisTemplate;

    private static final String key = "SHOP_STATUS";

    /**
     * 设置店铺营业状态
     * @param status
     * @return
     */
    @PutMapping("/{status}")
    @ApiOperation("设置店铺营业状态")
    public Result<String> setStatus(@PathVariable Integer status) {
        log.info("店铺的营业状态为:{}", status == 1 ? "营业中" : "打烊中");
        redisTemplate.opsForValue().set(key, status);
        return Result.success();
    }

    /**
     * 管理端查询营业状态
     */
    @GetMapping("/status")
    @ApiOperation("管理端查询营业状态")
    public Result<Integer> getStatus() {
        Integer status = (Integer) redisTemplate.opsForValue().get(key);
        log.info("店铺营业状态:{}", status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}

用户端查询店铺状态接口开发

@RestController("userShopController")      // 这里是自定义了该Bean的名称,因为controller.admin和controller.user下
// 有同类名的ShopController,要将这两个类名在Spring Bean容器中区分开来
@RequestMapping("/user/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
    @Autowired
    private RedisTemplate redisTemplate;

    private static final String key = "SHOP_STATUS";

    /**
     * 用户端查询营业状态
     */
    @GetMapping("/status")
    @ApiOperation("用户端查询营业状态")
    public Result<Integer> getStatus() {
        Integer status = (Integer) redisTemplate.opsForValue().get(key);
        log.info("店铺营业状态:{}", status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}

Read more

如何设计秒杀系统

什么是秒杀设计 秒杀设计 是一种针对高并发、大流量场景的系统设计,通常应用于电商活动中(如限时抢购、促销等),用户在非常短的时间内大量涌入系统,抢购有限的商品或优惠。这种场景下,系统需要能够承受巨大的瞬时并发请求,同时保证数据的一致性和业务的正确性。 秒杀技术分析 秒杀系统贯穿活动、商品和下单三个领域,涵盖了页面静态化、接口限流、Redis预减库存、异步下单和接口动态化等技术。 需要迎接的挑战有: 1. 高并发和压力测试:秒杀活动会带来巨大的流量,服务器和数据库的并发处理能力是关键。 2. 保证数据一致性:抢购涉及到商品库存的实时减少,需要保证在高并发场景下库存数据的准确性和一致性 3. 防止超卖和重复购买:确保同一商品不会被重复购买,同时避免超卖,即使是在极端的高并发情况下 4. 分布式锁和限流:使用分布式锁来保护关键资源,限制用户访问频率以免系统崩溃 5. 性能优化:包括代码层面的优化、数据库的优化、缓存的使用等,以提高系统性能和响应速度。 页面静态化 秒杀页面静态化是将动态生成的秒杀页面转换为静态HTML页面,从而提高页面响应速度和系统性能

By Yucan Huang

分布式系统下雪花算法生成全局唯一ID

雪花算法 在分布式系统中,为了避免多个节点生成相同的唯一标识符id,我们通常使用一种全局唯一ID生成策略,而Snowflake Algorithm就是广泛使用的解决方案之一。 雪花算法简介 雪花算法生成的 ID 是一个 64 位的二进制整数,具有以下组成部分: 部分 字节长度 描述 符号位 1bit 固定为0,因为生成的ID是整数 时间戳 41bits 表示时间戳,单位是毫秒,可以存储大约68年的时间 机器ID 10bits 用于标识不同的机器(节点)。10位可以将其分数据中心ID(5位)和机器ID(5位),共支持1024台机器同时生成ID 序列号 12bits 同一毫秒内的序列号,用来区分在同一台机器、同一时间戳下生成的多个ID,最大支持同一毫秒生成4096个唯一ID 代码实现 package com.can.springbootmessage; public class SnowflakeIdGenerator { // 起始时间戳,Long是64位,

By Yucan Huang

责任链设计模式

什么是责任链模式 项目有个请求,需要有对应的服务来处理,然后这个请求可能需要被很多个层级权限的服务来处理。我们将这些处理该请求的服务放在一条链上,链从前往后,是层级更高的服务,第一个服务处理不了,传递到链上的下一个服务,直到这个请求被处理成功。 责任链模式(Chain of Responsibility)是一种处理请求的模式,它让多个处理器都有机会处理该请求,直到其中某个处理器成功处理该请求,责任链模式把多个处理器串成链,然后让请求在链上传递。 1. 如何把多个处理器串成链,然后让请求在链上传递 客户端发送请求,处理类去处理它的请求,所以有个处理方法(handleRequest),处理类要连接在一起,所以**处理类要有一个方法(成员变量nextHandler)**指向下一个处理类。 我们抽象出一个公共的父类,然后去定义不同的处理类,这些处理类通过nextHandler连接起来。 2. 代码演示 package interview.pattern; public class ChainRespPattern { Handl

By Yucan Huang