springboot+redis+rabbitmq实现模拟秒杀系统(附带docker安装mysql,rabbitmq,redis教程)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 在项目开发中,难免会遇到高并发问题,本文借助秒杀系统的模拟场景,旨在解决高并发问题。

 原理

秒杀与其他业务最大的区别在于,在秒杀的瞬间,系统的并发量和吞吐量会非常大,与此同时,网络的流量也会瞬间变大。

对于系统并发量变大问题,这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的。因此,我们能做的只能是减少对数据库的访问。例如,前端发出了100万个请求,通过我们的处理,最终只有10个会访问数据库,这样就会大大提升系统性能。再针对秒杀这种场景,因为秒杀商品的数量是有限的,因此这种做法刚好适用。

那么具体是如何来减少对数据库的访问的呢?

假如,某个商品可秒杀的数量是10,那么在秒杀活动开始之前,把商品的ID和数量加载到Redis缓存。当服务端收到请求时,首先预减Redis中的数量,如果数量减到小于0时,那么随后的访问直接返回秒杀失败的信息。也就是说,最终只有10个请求会去访问数据库。

如果商品数量比较多,比如1万件商品参与秒杀,那么就有1万*10=10万个请求并发去访问数据库,数据库的压力还是会很大。这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。这样做之后,前端用户的请求可能不会立即得到响应是成功还是失败,很可能得到的是一个排队中的返回值,这个时候,需要客户端去服务端轮询,因为我们不能保证一定就秒杀成功了。当服务端出队,生成订单以后,把用户ID和商品ID写到缓存中,来应对客户端的轮询就可以了。

这样处理以后,我们的应用是可以很简单的进行分布式横向扩展的,以应对更大的并发。

安装所需工具

虚拟机:docker安装mysql,rabbitmq,redis

虚拟机安装和docker安装我就不介绍了,网上都有教程。

1、docker安装mysql

[root@yk3 docker]# docker pull mysql

image.gif

image.gif

mysql镜像下载完成(因为我之前下载了mysql镜像,所以这里显示already exists)

使用命令:docker images查看下载的镜像

[root@yk3 docker]# docker images

image.gif

image.gif

红框里的就是mysql镜像。

使用mysql镜像制作容器并运行。

[root@yk3 docker]# docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root 14340cbfa999

image.gif

这个14340cbfa999是我刚才下载的mysql镜像的id,也就是IMAGE ID字段对应的值,读者可以根据自己的id自行更换。

注意一定要加上-e MYSQL_ROOT_PASSWORD=数据库密码,不然启动不起来。

启动完成。使用docker ps -a命令查看运行中的容器。

image.gif

使用本地的navicat连接虚拟机docker中的mysql

image.gif

如果连接错误,报2058错误,解决方法:

docker进入mysql容器:

[root@yk3 docker]# docker exec -it e9023467ecfb /bin/bash

image.gif

注意:-it后面跟上mysql的容器id

image.gif

然后登陆mysql:

image.gif

进入mysql容器执行命令:

mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';

image.gif

执行成功,exit返回。再用navicat连接即可成功。

2、docker安装redis

同样是docker先下载redis镜像

[root@yk3 docker]# docker pull redis

image.gif

制作容器并运行:(注意镜像id改为自己的)

[root@yk3 docker]# docker run --name myredis -d -p 6379:6379 a617c1c92774

image.gif

使用redis-plus连接测试:

image.gif

连接成功!

3、docker安装rabbitmq

同样是docker先下载rabbitmq镜像

[root@yk3 docker]# docker pull rabbitmq

image.gif

制作容器并运行:(注意镜像id改为自己的)

[root@yk3 docker]# docker run -d --name rabbit -p 15672:15672 -p 5672:5672 603fe110af88

image.gif

这里要两个端口号:15672和5672,15672对应的是http(也就是我们登录RabbitMQ后台管理时用的端口),5672对应的是RabbitMQ的通信。

到这一步,rabbitmq已经启动完毕,但是在本地访问客户端http://192.168.121.130:15672这个端口还需要以下配置,

进入容器:

[root@yk3 docker]# docker exec -it 603fe110af88 /bin/bash

image.gif

执行命令:rabbitmq-plugins enable rabbitmq_management便可访问

image.gif

登录:用户名密码都为guest。

项目代码

新建一个springboot项目,项目结构:

image.gif

application.properties:

# DB Configuration
#指定数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库jdbc连接url地址,serverTimezone设置数据库时区东八区
spring.datasource.url=jdbc:mysql://192.168.121.130:3306/mqtest?serverTimezone=GMT%2B8
#数据库账号
spring.datasource.username=root
spring.datasource.password=root
server.port=8990
#rabbitmq
spring.rabbitmq.host=192.168.121.130
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#redis配置
spring.redis.host=192.168.121.130
spring.redis.port=6379
spring.redis.jedis.pool.max-active=1024
spring.redis.jedis.pool.max-wait=-1s
spring.redis.jedis.pool.max-idle=200
#mybatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
#打印sql语句
logging.level.com.rabbitmq.mapper=debug

image.gif

启动类:(启动时将库存加入到redis中)

package com.rabbitmq;
import com.rabbitmq.config.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    @Autowired
    private RedisService redisService;
    /**
     * redis初始化各商品的库存量
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        redisService.put("watch", 10000, 20);
    }
}

image.gif

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rabbitmq</groupId>
    <artifactId>consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consumer</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--jar依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <!-- bulid插件 编译mapper层下的xml -->
        <resources>
            <resource>
                <directory>src/main/java/</directory>
                <includes>
                    <include>com/rabbitmq/mapper/**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

image.gif

表文件:

CREATE TABLE `stock` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `stock` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
CREATE TABLE `t_order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `order_user` int DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=69406 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

image.gif

对应的实体类:

stock实体类:

package com.rabbitmq.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class Stock implements Serializable {
    private static final long serialVersionUID = 6235666939721331057L;
    Integer id;
    String name;
    Integer stock;
}

image.gif

order实体类:

package com.rabbitmq.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class Order implements Serializable {
    private static final long serialVersionUID = -8271355836132430489L;
    Integer id;
    String orderName;
    String orderUser;
}

image.gif

mapper接口文件:

package com.rabbitmq.mapper;
import com.rabbitmq.bean.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
    Integer insert(Order order);
}

image.gif

package com.rabbitmq.mapper;
import com.rabbitmq.bean.Stock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface StockMapper {
    List<Stock> selectList(@Param("name") String name);
    Integer updateByPrimaryKey(Stock stock);
}

image.gif

mapper对应的xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rabbitmq.mapper.OrderMapper">
    <insert id="insert" parameterType="com.rabbitmq.bean.Order">
        insert t_order(order_name,order_user) value (#{orderName},#{orderUser})
    </insert>
</mapper>

image.gif

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rabbitmq.mapper.StockMapper">
    <select id="selectList" resultType="com.rabbitmq.bean.Stock">
        select * from stock where name = #{name}
    </select>
    <update id="updateByPrimaryKey" parameterType="com.rabbitmq.bean.Stock">
        update stock set stock = #{stock} where id = #{id};
    </update>
</mapper>

image.gif

普通请求的service(为了方便对比,没有使用rabbitmq):

package com.rabbitmq.service;
import com.rabbitmq.bean.Order;
import com.rabbitmq.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService{
    @Autowired
    private OrderMapper orderMapper;
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
}

image.gif

package com.rabbitmq.service;
import com.rabbitmq.bean.Stock;
import com.rabbitmq.mapper.StockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Service
@Slf4j
public class StockService {
    @Autowired
    private StockMapper stockMapper;
    public void decrByStock(String stockName) {
        synchronized(this) {
            List<Stock> stocks = stockMapper.selectList(stockName);
            if (!CollectionUtils.isEmpty(stocks)) {
                Stock stock = stocks.get(0);
                stock.setStock(stock.getStock() - 1);
                stockMapper.updateByPrimaryKey(stock);
            }
        }
    }
    public Integer selectByName(String stockName) {
        synchronized (this){
            List<Stock> stocks = stockMapper.selectList(stockName);
            if (!CollectionUtils.isEmpty(stocks)) {
                return stocks.get(0).getStock().intValue();
            }
            return 0;
        }
    }
}

image.gif

接下来重点来了,使用rabbitmq实现:

rabbitmq配置类:

package com.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitMQConfig {
    //库存交换机
    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";
    //订单交换机
    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";
    //库存队列
    public static final String STORY_QUEUE = "STORY_QUEUE";
    //订单队列
    public static final String ORDER_QUEUE = "ORDER_QUEUE";
    //库存路由键
    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";
    //订单路由键
    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    //创建库存交换机
    @Bean
    public Exchange getStoryExchange() {
        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
    }
    //创建库存队列
    @Bean
    public Queue getStoryQueue() {
        return new Queue(STORY_QUEUE);
    }
    //库存交换机和库存队列绑定
    @Bean
    public Binding bindStory() {
        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
    }
    //创建订单队列
    @Bean
    public Queue getOrderQueue() {
        return new Queue(ORDER_QUEUE);
    }
    //创建订单交换机
    @Bean
    public Exchange getOrderExchange() {
        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
    }
    //订单队列与订单交换机进行绑定
    @Bean
    public Binding bindOrder() {
        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
    }
}

image.gif

redis配置类:

package com.rabbitmq.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

image.gif

redis的操作类:

package com.rabbitmq.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 设置String键值对
     * @param key
     * @param value
     * @param millis
     */
    public void put(String key, Object value, long millis) {
        redisTemplate.opsForValue().set(key, value, millis, TimeUnit.MINUTES);
    }
    public void putForHash(String objectKey, String hkey, String value) {
        redisTemplate.opsForHash().put(objectKey, hkey, value);
    }
    /**
     * 对指定key的键值减一
     * @param key
     * @return
     */
    public Long decrBy(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }
}

image.gif

重点:rabbitmq实现的service层:

package com.rabbitmq.mqservice;
import com.rabbitmq.config.MyRabbitMQConfig;
import com.rabbitmq.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MQOrderService {
    @Autowired
    private OrderService orderService;
    /**
     * 监听订单消息队列,并消费
     * @param order
     */
    @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
    public void createOrder(Order order) {
        log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrderUser(), order.getOrderName());
        /**
         * 调用数据库orderService创建订单信息
         */
        orderService.createOrder(order);
    }
}

image.gif

package com.rabbitmq.mqservice;
import com.rabbitmq.config.MyRabbitMQConfig;
import com.rabbitmq.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MQStockService {
    @Autowired
    private StockService stockService;
    /**
     * 监听库存消息队列,并消费
     * @param stockName
     */
    @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
    public void decrByStock(String stockName) {
        log.info("库存消息队列收到的消息商品信息是:{}", stockName);
        /**
         * 调用数据库service给数据库对应商品库存减一
         */
        stockService.decrByStock(stockName);
    }
}

image.gif

controller层:

package com.rabbitmq.controller;
import com.rabbitmq.bean.Order;
import com.rabbitmq.config.MyRabbitMQConfig;
import com.rabbitmq.config.RedisService;
import com.rabbitmq.service.OrderService;
import com.rabbitmq.service.StockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Slf4j
public class SecController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisService redisService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private StockService stockService;
    /**
     * 使用redis+消息队列进行秒杀实现
     * @param username
     * @param stockName
     * @return
     */
    @RequestMapping("/sec")
    @ResponseBody
    public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {
        log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
        String message = null;
        //调用redis给相应商品库存量减一
        Long decrByResult = redisService.decrBy(stockName);
        if (decrByResult >= 0) {
            /**
             * 说明该商品的库存量有剩余,可以进行下订单操作
             */
            log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作", username, stockName);
            //发消息给库存消息队列,将库存数据减一
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, stockName);
            //发消息给订单消息队列,创建订单
            Order order = new Order();
            order.setOrderName(stockName);
            order.setOrderUser(username);
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, order);
            message = "用户" + username + "秒杀" + stockName + "成功";
        } else {
            /**
             * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
             */
            log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username);
            message = username + "商品的库存量没有剩余,秒杀结束";
        }
        return message;
    }
    /**
     * 实现纯数据库操作实现秒杀操作
     * @param username
     * @param stockName
     * @return
     */
    @RequestMapping("/secDataBase")
    @ResponseBody
    public String secDataBase(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {
        synchronized (this){
            redisService.decrBy(stockName);
            log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
            String message = null;
            //查找该商品库存
            Integer stockCount = stockService.selectByName(stockName);
            log.info("用户:{}参加秒杀,当前商品库存量是:{}", username, stockCount);
            if (stockCount > 0) {
                /**
                 * 还有库存,可以进行继续秒杀,库存减一,下订单
                 */
                //1、库存减一
                stockService.decrByStock(stockName);
                //2、下订单
                Order order = new Order();
                order.setOrderUser(username);
                order.setOrderName(stockName);
                orderService.createOrder(order);
                log.info("用户:{}.参加秒杀结果是:成功", username);
                message = username + "参加秒杀结果是:成功";
            } else {
                log.info("用户:{}.参加秒杀结果是:秒杀已经结束", username);
                message = username + "参加秒杀活动结果是:秒杀已经结束";
            }
            return message;
        }
    }
}

image.gif

代码编写完毕,启动运行。

测试

使用工具 apache-jmeter-5.2.1

如何使用工具 可参考Apache JMeter5.2基础入门实践详解

    1. 启动apache-jmeter-5.2.1
    2. 设置语言
    3. image.gif
    4. 创建线程组
    5. image.gif

    添加http请求和监听原件

    image.gif

    jmeter可以定义随机参数

    image.gif

    image.gif

    image.gif

    image.gif

    点击绿色箭头运行

    image.gif

    相关实践学习
    基于Redis实现在线游戏积分排行榜
    本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
    云数据库 Redis 版使用教程
    云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
    目录
    相关文章
    |
    21天前
    |
    分布式计算 关系型数据库 MySQL
    SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
    SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型 图像处理 光通信 分布式计算 算法语言 信息技术 计算机应用
    37 8
    |
    22天前
    |
    SQL 前端开发 关系型数据库
    SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
    SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
    49 9
    |
    2月前
    |
    消息中间件 编解码 Docker
    【Docker项目实战】Docker部署RabbitMQ消息中间件
    【10月更文挑战第8天】Docker部署RabbitMQ消息中间件
    103 1
    【Docker项目实战】Docker部署RabbitMQ消息中间件
    |
    2月前
    |
    NoSQL Java Redis
    redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
    这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
    58 1
    redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
    |
    2月前
    |
    缓存 NoSQL Java
    springboot的缓存和redis缓存,入门级别教程
    本文介绍了Spring Boot中的缓存机制,包括使用默认的JVM缓存和集成Redis缓存,以及如何配置和使用缓存来提高应用程序性能。
    116 1
    springboot的缓存和redis缓存,入门级别教程
    |
    2月前
    |
    Java 关系型数据库 MySQL
    springboot学习五:springboot整合Mybatis 连接 mysql数据库
    这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
    109 0
    springboot学习五:springboot整合Mybatis 连接 mysql数据库
    |
    2月前
    |
    Java 关系型数据库 MySQL
    springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
    这篇文章是关于如何使用Spring Boot框架通过JdbcTemplate操作MySQL数据库的教程。
    35 0
    springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
    |
    2月前
    |
    JSON NoSQL Java
    springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
    该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
    |
    2月前
    |
    NoSQL Java Redis
    shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
    这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
    31 0
    shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
    |
    1月前
    |
    关系型数据库 MySQL Java
    SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
    SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
    27 0