Redis数据库测试和缓存穿透、雪崩、击穿
Redis数据库测试实验
实验要求
1.新建一张user表,在表内插入10000条数据。
2.①通过jdbc查询这10000条数据,记录查询时间。
②通过redis查询这10000条数据,记录查询时间。
3.①再次查询这一万条数据,要求根据年龄进行排序,mysql和redis各实现一次。
4.上面排序后的前5人可进行抽奖,每人有一次抽奖机会,抽奖奖品随意设计,抽奖方式通过redis实现。
1.基本准备
先下载好jar包
在根目录下,新建lib文件夹,并将两个jar包移动到lib文件夹中
在IDEA中,右键点击lib,选择“添加为库”
两个jar包显示可展开即为成功。
2.mysql建立用户表user
CREATE TABLE `user` (
`id` int primary key AUTO_INCREMENT,
`name` varchar(10) COMMENT '姓名',
`age` int COMMENT '年龄'
) ;
3.为mysql和redis添加数据
(1)获取数据库连接,并为mysql添加数据
//获取数据库连接
public Connection getConnection() {
System.out.println("获取数据库连接");
String url = "jdbc:mysql://localhost:3306/homework";
String username = "root";
String password = "123456";
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//mysql添加数据
public void addMysql() {
System.out.println("mysql添加数据");
Connection conn = null;
PreparedStatement ps = null;
conn = getConnection();
try {
Random random = new Random();
for (int i = 0; i < 10000; i++) {
String name = "Name" + i;
int age = random.nextInt(100) + 1;
ps = conn.prepareStatement("INSERT INTO user (name,age) VALUES (?,?)");
ps.setString(1, name);
ps.setInt(2, age);
ps.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
ps.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
(2)将Mysql数据转储到redis中
// 将Mysql数据库数据转储到Redis
public void addRedis() {
System.out.println("redis添加数据");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
conn = getConnection();
try {
ps = conn.prepareStatement("select * from user");
rs = ps.executeQuery();
Jedis jedis = new Jedis("localhost", 6379);
while (rs.next()) {
String id = String.valueOf(rs.getInt("id"));
String name = rs.getString("name");
int age = rs.getInt("age");
// 使用有序集合存储学生ID和年龄,以便进行排序
jedis.zadd("UserByAge", age, id);
// 存储学生数据
jedis.hset("user:" + id, "name", name);
jedis.hset("user:" + id, "age", String.valueOf(age));
}
jedis.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
4.实现mysql和redis查询,并比较查询时间
(1)mysql查询
//mysql查询
public void queryDataWithJDBC() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
conn = getConnection();
try {
ps = conn.prepareStatement("select * from user");
rs = ps.executeQuery();
while (rs.next()) {
// System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name") + ", Age: " + rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
(2)redis查询
//redis查询
public void queryDataWithRedis() {
Jedis jedis = new Jedis("localhost", 6379);
Set<String> keys = jedis.keys("user:*");
for (String key : keys) {
Map<String, String> user = jedis.hgetAll(key);
// System.out.println("Key: " + key + ", Value: " + user);
}
jedis.close();
}
(3)记录并比较查询时间
// 比较查询时间
public void compareTime() {
// 通过jdbc查询这10000条数据,记录查询时间
long start = System.currentTimeMillis();
queryDataWithJDBC();
long end = System.currentTimeMillis();
System.out.println("JDBC查询时间: " + (end - start) + "ms");
// 通过redis查询这10000条数据,记录查询时间
start = System.currentTimeMillis();
queryDataWithRedis();
end = System.currentTimeMillis();
System.out.println("Redis查询时间: " + (end - start) + "ms");
}
5.根据年龄进行排序
(1)mysql排序
//mysql实现排序
public void queryAndSortDataWithJDBC() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
conn = getConnection();
try {
ps = conn.prepareStatement("SELECT * FROM user ORDER BY age");
rs = ps.executeQuery();
System.out.println("mysql实现排序:");
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name") + ", Age: " + rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
(2)redis排序
//redis实现排序
public void queryAndSortDataWithRedis() {
Jedis jedis = new Jedis("localhost", 6379);
List<Tuple> users = jedis.zrangeWithScores("UserByAge", 0, -1);
System.out.println("redis实现排序:");
for (Tuple user : users) {
String id = user.getElement();
double age = user.getScore();
String name = jedis.hget("user:" + id, "name");
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + (int) age);
}
jedis.close();
}
6.抽奖功能
//抽奖
public void lottery() {
Jedis jedis = new Jedis("localhost", 6379);
// 添加奖品
String[] prizes = {"锅", "碗", "瓢", "盆", "金元宝"};
for (String prize : prizes) {
jedis.sadd("prizes", prize);
}
// 年龄最小的前5人
System.out.println("年龄最小的前5人:");
List<Tuple> youngestUsers = jedis.zrangeWithScores("UserByAge", 0, 4);
for (Tuple user : youngestUsers) {
String id = user.getElement();
double age = user.getScore();
String name = jedis.hget("user:" + id, "name");
String prize = jedis.srandmember("prizes");
System.out.println("恭喜 " + name + " 获得了抽奖机会!奖品是:" + prize);
}
jedis.close();
}
7.主函数
public static void main(String[] args) throws SQLException {
JedisHomework jedisHomework = new JedisHomework();
jedisHomework.addMysql();
jedisHomework.addRedis();
jedisHomework.compareTime();
jedisHomework.queryAndSortDataWithJDBC();
jedisHomework.queryAndSortDataWithRedis();
jedisHomework.lottery();
}
Redis中的缓存穿透、雪崩、击穿的原因以及解决方案
1.缓存击穿
(1)产生原因
在高并发访问下,某个热点key在缓存中过期后,大量并发请求同时查询数据库,导致数据库压力激增的现象。
(2)解决方案
合理的过期时间:将热点数据设置为永远不过期
使用互斥锁:基于redis or zookeeper实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其他请求才能通过该key访问数据。
2.缓存雪崩
(1)产生原因
由于缓存服务器在同一时间大面积失效或宕机,导致大量请求直接打到数据库,瞬间引发数据库压力激增,甚至导致数据库崩溃。
(2)解决方案
事前:redis 高可用,主从+哨兵,redus cluster,避免全盘崩溃
事中:本地缓存 + hystrix 限流&降级,避免 MySQL被打死。同时设置合理的过期时间。
事后:redis持久化,一旦重启,自动从磁盘上加载数据,快速回复缓存数据。
3.缓存穿透
(1)产生原因
查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
缓存穿透很有可能是黑客攻击所为,黑客通过发送大量的高并发的无法响应的请求给服务器,由于请求的资源根本就不存在,DB(数据库)就很容易被打垮了。
(2)解决方案
缓存空对象:对查询结果为空的情况,也将其缓存起来,并设置合理的过期时间。
参数校验:在接收到请求之前进行参数校验,判断请求参数是否合法。
布隆过滤器:判断请求的参数是否存在于缓存或数据库中。
4.三者的异同
相同点:大量的请求在redis上得不到响应,那么就会导致这些请求会直接去访问DB,导致DB的压力瞬间变大而卡死或者宕机。
不同点:缓存击穿是某个热点过期后,导致大量请求访问DB;
缓存雪崩是多个key过期后,导致大量请求访问DB;
缓存穿透是不存在的key收到大量请求,每次请求都要到DB查询。