1. Redis在缓存中的应用
2. 简单实现Spring/Spring-Boot集成Redis(通过Jedis)
- 假设有以下这样的一个数据库
create table tb_user(
id int(11) NOT NULL AUTO_INCREMENT,
name char(20) NOT NULL DEFAULT '',
age int(11) NOT NULL,
img varchar(128) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET = utf8;
INSERT INTO tb_user (id, `name`, age, img) VALUES (1, 'neco', 18, '/photo/user/neco.png');
- 项目中加入以下依赖(这里只展示集成的部分,对于访问数据库的流程,这里不再描述说明)
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
- 创建JedisConfig类,配置Bean
package cn.lazyfennec.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/8/21 10:45
*/
@Configuration
public class JedisConfig {
// 创建对象,spring托管,即放入spring容器,使其支持注入
@Bean
public JedisPool jedisPool() {
JedisPool jedisPool = new JedisPool("192.168.1.7", 6379);
return jedisPool;
}
}
- 创建UserController
package cn.lazyfennec.redis.controller;
import cn.lazyfennec.redis.entity.User;
import cn.lazyfennec.redis.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/8/21 10:38
*/
@RestController
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("/user/{id}")
public User findUserById(@PathVariable Integer id) {
return userService.findUserById(id);
}
}
- 创建UserServiceImpl
package cn.lazyfennec.redis.service.impl;
import cn.lazyfennec.redis.entity.User;
import cn.lazyfennec.redis.mapper.UserMapper;
import cn.lazyfennec.redis.service.IUserService;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.annotation.Resource;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/8/21 10:40
*/
@Service
public class UserServiceImpl implements IUserService {
@Resource
private UserMapper userMapper;
@Autowired
private JedisPool jedisPool;
@Override
public User findUserById(Integer id) {
User user = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String userStr = jedis.get("user_" + id); // 尝试获取数据
if (userStr != null && !userStr.isEmpty()) { // 如果获取到有效数据,则转换后返回
user = JSONObject.parseObject(userStr, User.class);
} else {// 如果没有获取到数据,则查询数据库返回
user = userMapper.findUserById(id);
if (user != null) jedis.set("user_" + id, JSONObject.toJSONString(user)); // 设置到redis中
}
} finally {
// 记得关闭Jedis,因为这里使用的是JedisPool,所以这里的关闭并不是直接关闭连接,而是释放,以供其他的业务使用
if (jedis != null) jedis.close();
}
return user;
}
}
- 总结
对于这个例子,我们可以得知,在读多写少的情况下,利用redis缓存,可以有效的解决高并发的问题,对数据库的访问进行限流。
3. 通过benchmark进行基准测试
[root@lazyfennec redis-5.0.14]# redis-benchmark --help
Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]
-h <hostname> Server hostname (default 127.0.0.1)
-p <port> Server port (default 6379)
-s <socket> Server socket (overrides host and port)
-a <password> Password for Redis Auth
-c <clients> Number of parallel connections (default 50)
-n <requests> Total number of requests (default 100000)
-d <size> Data size of SET/GET value in bytes (default 3)
--dbnum <db> SELECT the specified db number (default 0)
-k <boolean> 1=keep alive 0=reconnect (default 1)
-r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD
Using this option the benchmark will expand the string __rand_int__
inside an argument with a 12 digits number in the specified range
from 0 to keyspacelen-1. The substitution changes every time a command
is executed. Default tests use this to hit random keys in the
specified range.
-P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).
-e If server replies with errors, show them on stdout.
(no more than 1 error per second is displayed)
-q Quiet. Just show query/sec values
--csv Output in CSV format
-l Loop. Run the tests forever
-t <tests> Only run the comma separated list of tests. The test
names are the same as the ones produced as output.
-I Idle mode. Just open N idle connections and wait.
Examples:
Run the benchmark with the default configuration against 127.0.0.1:6379:
$ redis-benchmark
Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
$ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20
Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
$ redis-benchmark -t set -n 1000000 -r 100000000
Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
$ redis-benchmark -t ping,set,get -n 100000 --csv
Benchmark a specific command line:
$ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0
Fill a list with 10000 random elements:
$ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__
On user specified command lines __rand_int__ is replaced with a random integer
with a range of values selected by the -r option.
测试:
- 下面是我虚拟机中的执行结果(内存和核心数比较小)
[root@lazyfennec redis-5.0.14]# redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
SET: 33129.59 requests per second
GET: 33604.97 requests per second
LPUSH: 33372.27 requests per second
LPOP: 33003.84 requests per second
- 在4核4G的云服务器中的执行结果如下:
[root@VM-4-15-centos bin]# redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
SET: 88613.20 requests per second
GET: 88570.04 requests per second
LPUSH: 88699.66 requests per second
LPOP: 89182.20 requests per second
- 如果加上Pipeline的配置
[root@VM-4-15-centos bin]# redis-benchmark -r 1000000 -n 2000000 -P 4 -t get,set,lpush,lpop
====== SET ======
2000000 requests completed in 6.08 seconds
50 parallel clients
3 bytes payload
keep alive: 1
98.82% <= 1 milliseconds
99.99% <= 2 milliseconds
100.00% <= 3 milliseconds
100.00% <= 3 milliseconds
329163.91 requests per second
====== GET ======
2000000 requests completed in 5.78 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.68% <= 1 milliseconds
100.00% <= 2 milliseconds
100.00% <= 2 milliseconds
346200.44 requests per second
====== LPUSH ======
2000000 requests completed in 5.83 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.63% <= 1 milliseconds
100.00% <= 1 milliseconds
342935.53 requests per second
====== LPOP ======
2000000 requests completed in 6.42 seconds
50 parallel clients
3 bytes payload
keep alive: 1
98.49% <= 1 milliseconds
99.99% <= 2 milliseconds
100.00% <= 2 milliseconds
311526.47 requests per second
根据测试结果可以看到,执行效率算是非常高了,其实从这里就可以看出来,redis是完全可以实现支撑10w/s的并发查询的。
3. 数据结构 - String
String数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。
使用场景:微博数,粉丝数(常规计数)等
常用命令:
命令 | 说明 |
---|---|
GET | 获取指定key的值 |
SET | 设置指定key的值 |
INCR | 将key中存储的数字值增一 |
DECR | 将key中存储的数字值减一 |
MGET | 获取所有(一个或多个)给定key的值 |
场景1: 粉丝数或者点赞数或浏览数增加的情况
如某个直播场景中,某个主播同一时间有多个人对该主播进行关注操作,那么将会触发一个粉丝数+1的请求,这个时候如何保证数据的准确性?
- 答:redis命令的执行是一条一条执行的,虽然可能前端同时发起关注请求,但是到redis的时候,还是会根据前后顺序一条一条执行。
针对命令处理来说,redis是单线程的!
这样就保证了其操作的原子性
4. 数据结构 - Hash
Hash是一个string类型的field和value的映射表
使用场景: 存储部分变更数据,如用户信息等等
常用命令:
命令 | 说明 |
---|---|
HGET | 获取指定key的获取存储在哈希表中指定字段的值 |
HSET | 将哈希表key中的字段field的值设置为value |
HGETALL | 获取在哈希表中指定key的所有字段和值 |
场景1: 某种情况下,我们需要查询某一个用户的某一个/多个信息
如我们在缓存某个用户的信息的时候,可能碰到一种情况就是,我并不需要整个用户的所有信息,可能我只是想要查询某个字段的值,如name。为了提高效率,我们获取缓存数据的时候,可以只获取name的值(避免过多的数据传输消耗的资源)
- 实现的大致代码如下
@Override
public User findUserById(Integer id) {
User user = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Map<String, String> map = jedis.hgetAll("hash_user_" + id);// 尝试获取数据
if (map != null && map.size() > 0) { // 如果获取到有效数据,则转换后返回
user = new User();
user.setId(Integer.valueOf(map.get("id")));
user.setName(map.get("name"));
user.setAge(Integer.valueOf(map.get("age")));
user.setImg(map.get("img"));
} else {// 如果没有获取到数据,则查询数据库返回
user = userMapper.findUserById(id);
// 第一种方式,缺点,需要多次与redis进行交互
// jedis.hset("hash_user_" + id, "id", String.valueOf(user.getId()));
// jedis.hset("hash_user_" + id, "name", user.getName());
// jedis.hset("hash_user_" + id, "age", String.valueOf(user.getAge()));
// jedis.hset("hash_user_" + id, "img", String.valueOf(user.getImg()));
// 第二种方式,优化的方式
HashMap<String, String> hmap = new HashMap<>();
hmap.put("id", String.valueOf(user.getId()));
hmap.put("name", user.getName());
hmap.put("age", String.valueOf(user.getAge()));
hmap.put("img", user.getImg());
jedis.hmset("hash_user_" + id, hmap);
}
} finally {
// 记得关闭Jedis,因为这里使用的是JedisPool,所以这里的关闭并不是直接关闭连接,而是释放,以供其他的业务使用
if (jedis != null) jedis.close();
}
return user;
}
@Override
public String findUserNameById(Integer id) {
Jedis jedis = null;
String name = null;
try {
jedis = jedisPool.getResource();
name = jedis.hget("hash_user_" + id, "name"); // 尝试获取数据
if(name != null && !name.isEmpty()) {
name = userMapper.findUserNameById(id);
}
} finally {
// 记得关闭Jedis,因为这里使用的是JedisPool,所以这里的关闭并不是直接关闭连接,而是释放,以供其他的业务使用
if (jedis != null) jedis.close();
}
return name;
}
如果觉得有收获,欢迎点赞和评论,更多知识,请点击关注查看我的主页信息哦~
网友评论