mysql+redis实现排行榜

前言

本文参考自:(值得参考,线上可用)Mysql(jdbc连接池) + Redis(连接池)实现排行榜实战,感谢

代码逻辑:把mysql中的玩家分数表的数据导到redis中,使用redis中的有序集合zset来实现数据递减排行并返回结果(排行榜)

我们要明确用redis做排行榜的意义,如果在mysql中有一张游戏的玩家分数表,那么我们用简单的sql语句就能实现数据排行的功能,为什么还要用redis做数据排行?首先mysql等关系型数据库做大数据量的数据查询排序是有性能瓶颈的,而redis是基于内存的键值数据库,其查询、排序的运算速度要比mysql等关系型数据库要快得多;并且redis中内置了一个有序集合(zset)的数据结构,zset里面的元素是唯一的,按分数字段从小到大排序,是有序的,非常适合用于实现排行榜功能。

准备

启动mysql,在mysql中准备一张玩家表,表中存储着玩家的名字和相应的分数
在这里插入图片描述

启动redis

启动时选择关闭保护模式,也可以启动之前到redis.conf注释掉bind 127.0.0.1并把protected-mode置为no)

cd /usr/local/redis-5.0.4/src
./redis-server --protected-mode no
代码实现

项目结构

在这里插入图片描述

pom.xml

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.14</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.0.1</version>
</dependency>
  </dependencies>

mysql连接池配置文件

在使用6.0以上的jdbc连接驱动连接mysql时,需要在url里指定时区(我的mysql和jdbc都是8.0以上的)

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/new_schema?serverTimezone=UTC
username=root
password=root
initialSize=3
maxActive=20
maxWait=3000

initialSize:连接池启动时要初始化多少个连接。
maxWait:连接池出借连接的最长期限,单位是毫秒,比如设为10000ms,客户端从连接池获取(借出)一个连接后,10000毫秒没有归还(return),则连接池会抛出异常。

maxActive:配置连接池同时能维持的最大连接数,如果客户端理论上需要100个连接,则这个值设为100

jedis连接池配置文件

host=192.168.100.10
port=6379
maxTotal=50
maxIdle=10

host和post为redis服务所在的ip地址和端口号

maxIdle的意思是连接池中空闲连接的最大数量

maxTotal是连接池中总连接的最大数量

JDBCUtils.java

实现mysql连接池

package redis_demo.demo2;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
 
public class JDBCUtils {
    private static DataSource ds;
 
    static {
        try {
        	//加载连接池的配置文件
            Properties pro = new Properties();
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            //生成连接池
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
 
    public static DataSource getDataSource() {
        return ds;
    }
 
    public static void close(ResultSet rs, PreparedStatement pstmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        if (pstmt != null) {
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        if (conn != null) {
            try {
                // 关闭连接,用完就关闭
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

RedisUtils.java

实现jedis连接池

package redis_demo.demo2;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class RedisUtils {
   private static JedisPool jedisPool;

   static {
	   //加载redis连接池的配置文件
       InputStream is = RedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
       Properties pro = new Properties();
       try {
           pro.load(is);
       } catch (IOException e) {
           e.printStackTrace();
       }
       
       JedisPoolConfig config = new JedisPoolConfig();
       config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
       config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
	   //生成连接池对象
       jedisPool = new JedisPool(config, pro.getProperty("host"), Integer.parseInt(pro.getProperty("port")));
   }

   public static Jedis getJedis() {
       return jedisPool.getResource();
   }
}

User.java

用于封装数据元素形成user对象

package redis_demo.demo2;

//创建user类,封装数据元素形成对象
public class User {
	public int id;
	public String name;
	public int gold;
	
	public String toString() {
        return "User{" +
                "id=" + id +
                ", uname='" + name + '\'' +
                ", gold=" + gold +
                '}';
	}
}

MysqlConn.java

从mysql连接池中获取连接,连接mysql,根据传入的id获得相应的数据元素封装成user对象

package redis_demo.demo2;

import java.awt.print.Printable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import redis_demo.demo2.JDBCUtils;

public class MysqlConn {
	public static User getUserByUid(int paramUid) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
        	//从连接池获取连接
            conn = JDBCUtils.getConnection();
            //执行sql语句
            String sql = "select * from persons where id = ?";
            pstmt = conn.prepareStatement(sql);
            //设定sql语句的参数
            pstmt.setInt(1, paramUid);
            //返回结果集
            rs = pstmt.executeQuery();
            
            User user = null;
            if (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int gold = rs.getInt("gold");
 
                user = new User();
                user.id = id;
                user.name = name;
                user.gold = gold;
            }
            System.out.println(user);
            return user;
        } catch (Exception e) {
            return null;
        } finally {
            JDBCUtils.close(rs, pstmt, conn);
        }
	}
}

RedisConn.java

通过jedis连接redis,添加成员到有序集合,并从有序集合中返回按分数递减排序的成员集合

package redis_demo.demo2;

import java.util.Set;
import redis.clients.jedis.Jedis;

public class RedisConn {
	//添加成员到有序集合
    public static void addToRank(String name, int gold) {
        Jedis jedis = RedisUtils.getJedis();
        jedis.zadd("rank", gold, name);
        jedis.close();
    }
 
    //从有序集合中获取按分数递减排序的成员集合
    public static Set<String> getRank() {
        Jedis jedis = RedisUtils.getJedis();
        Set<String> rankSet = jedis.zrevrange("rank", 0, -1);
        jedis.close();
        return rankSet;
    }
}

App.java

项目运行主类

循环persons表中的每一行数据,添加到redis中的有序集合中,再从redis的有序集合中按分数递减顺序返回排序好的成员集合

package redis_demo.demo2;

import java.util.Set;

public class App {
   public static void main(String[] args) {
	   //循环persons表中的每一行数据,添加到redis中的有序集合中
       for (int i = 1; i <= 3; i++) {
           User user = MysqlConn.getUserByUid(i);
           RedisConn.addToRank(user.name, user.gold);
       }

       Set<String> rankSet = null;
       
       //从redis的有序集合中按分数递减顺序返回排序好的成员集合
       rankSet = RedisConn.getRank();

       System.out.println(rankSet);
   }
}

项目运行结果

最后返回来的是按gold字段降序排序好的成员集合

在这里插入图片描述

实现排行榜的基础上,我们可以通过 Redis 的 ZRANK 命令获取某个对象的排名,进而结合 MySQL 的用户表进行分页输出。 具体实现步骤如下: 1. 根据排行榜名称(例如 "daily_ranking")从 Redis 中获取前 N 名用户; 2. 遍历每个用户,使用 ZRANK 命令获取其在排行榜中的排名; 3. 根据排名计算出分页的起始位置和结束位置; 4. 在 MySQL 的用户表中查询对应的用户信息; 5. 将查询结果进行分页输出。 例如,假设我们要实现每页显示 15 条记录的日榜排行榜,并且需要输出用户的 ID、得分、姓名和头像,可以使用以下代码实现: ```php // 获取前 N 名用户 $result = $redis->zrevrange('daily_ranking', 0, $n - 1, 'WITHSCORES'); // 遍历每个用户 foreach ($result as $i => $user) { // 获取用户 ID 和得分 $user_id = $user[0]; $score = $user[1]; // 使用 ZRANK 命令获取排名 $rank = $redis->zrank('daily_ranking', $user_id); // 计算分页的起始位置和结束位置 $start = $page * $page_size; $end = $start + $page_size - 1; // 在 MySQL 的用户表中查询用户信息 $stmt = $pdo->prepare("SELECT id, score, name, avatar FROM user WHERE id = :user_id"); $stmt->execute(['user_id' => $user_id]); $user_info = $stmt->fetch(PDO::FETCH_ASSOC); // 输出用户信息 if ($rank >= $start && $rank <= $end) { echo "<tr>"; echo "<td>" . ($rank + 1) . "</td>"; echo "<td>" . $user_info['id'] . "</td>"; echo "<td>" . $user_info['score'] . "</td>"; echo "<td>" . $user_info['name'] . "</td>"; echo "<td><img src=\"" . $user_info['avatar'] . "\" width=\"50\" height=\"50\"></td>"; echo "</tr>"; } } ``` 其中,$n 为需要获取的前 N 名用户数量,$page 为当前页码,$page_size 为每页显示的记录数。 需要注意的是,以上实现仅供参考,具体实现方式还要根据实际情况进行调整。此外,为了提高性能,可以使用 Redis 的缓机制,避免每次都从 MySQL 中查询用户信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值