Redis 是什么
面试官: 你先来说下 Redis 是什么吧
我:(这不就是总结下 Redis 的定义和特点嘛)Redis 是 C 语言开发的一个开源的 (遵从 BSD 协议) 高性能键值对 (key-value) 的内存数据库, 可以用作数据库, 缓存, 消息中间件等. 它是一种 NoSQL(not-only sql, 泛指非关系型数据库)的数据库.
我顿了一下, 接着说: Redis 作为一个内存数据库.
性能优秀, 数据在内存中, 读写速度非常快, 支持并发 10W QPS;
单进程单线程, 是线程安全的, 采用 IO 多路复用机制;
丰富的数据类型, 支持字符串 (strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 等;
支持数据持久化. 可以将内存中数据保存在磁盘中, 重启时加载;
主从复制, 哨兵, 高可用;
可以用作分布式锁;
可以作为消息中间件使用, 支持发布订阅
五种数据类型
面试官: 总结的不错, 看来是早有准备啊. 刚来听你提到 Redis 支持五种数据类型, 那你能简单说下这五种数据类型吗?
我: 当然可以, 但是在说之前, 我觉得有必要先来了解下 Redis 内部内存管理是如何描述这 5 种数据类型的. 说着, 我拿着笔给面试官画了一张图:
我: 首先 Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value,redisObject 最主要的信息如上图所示: type 表示一个 value 对象具体是何种数据类型, encoding 是不同数据类型在 Redis 内部的存储方式. 比如: type=string 表示 value 存储的是一个普通字符串, 那么 encoding 可以是 raw 或者 int.
我顿了一下, 接着说: 下面我简单说下 5 种数据类型:
1,string 是 Redis 最基本的类型, 可以理解成与 Memcached 一模一样的类型, 一个 key 对应一个 value.value 不仅是 string, 也可以是数字. string 类型是二进制安全的, 意思是 Redis 的 string 类型可以包含任何数据, 比如 jpg 图片或者序列化的对象. string 类型的值最大能存储 512M.
2,Hash 是一个键值 (key-value) 的集合. Redis 的 hash 是一个 string 的 key 和 value 的映射表, Hash 特别适合存储对象. 常用命令: hget,hset,hgetall 等.
3,list 列表是简单的字符串列表, 按照插入顺序排序. 可以添加一个元素到列表的头部 (左边) 或者尾部 (右边) 常用命令: lpush,rpush,lpop,rpop,lrange(获取列表片段) 等.
应用场景: list 应用场景非常多, 也是 Redis 最重要的数据结构之一, 比如 Twitter 的关注列表, 粉丝列表都可以用 list 结构来实现.
数据结构: list 就是链表, 可以用来当消息队列用. Redis 提供了 List 的 push 和 pop 操作, 还提供了操作某一段的 API, 可以直接查询或者删除某一段的元素.
实现方式: Redis list 的是实现是一个双向链表, 既可以支持反向查找和遍历, 更方便操作, 不过带来了额外的内存开销.
4,set 是 string 类型的无序集合. 集合是通过 hashtable 实现的. set 中的元素是没有顺序的, 而且是没有重复的.
常用命令: sdd,spop,smembers,sunion 等.
应用场景: Redis set 对外提供的功能和 list 一样是一个列表, 特殊之处在于 set 是自动去重的, 而且 set 提供了判断某个成员是否在一个 set 集合中.
5,zset 和 set 一样是 string 类型元素的集合, 且不允许重复的元素. 常用命令: zadd,zrange,zrem,zcard 等.
使用场景: sorted set 可以通过用户额外提供一个优先级 (score) 的参数来为成员排序, 并且是插入有序的, 即自动排序. 当你需要一个有序的并且不重复的集合列表, 那么可以选择 sorted set 结构. 和 set 相比, sorted set 关联了一个 double 类型权重的参数 score, 使得集合中的元素能够按照 score 进行有序排列, Redis 正是通过分数来为集合中的成员进行从小到大的排序.
实现方式: Redis sorted set 的内部使用 HashMap 和跳跃表 (skipList) 来保证数据的存储和有序, HashMap 里放的是成员到 score 的映射, 而跳跃表里存放的是所有的成员, 排序依据是 HashMap 里存的 score, 使用跳跃表的结构可以获得比较高的查找效率, 并且在实现上比较简单.
我: 我之前总结了一张图, 关于数据类型的应用场景, 如果您感兴趣, 可以去我的掘金看..
数据类型应用场景总结
面试官: 想不到你平时也下了不少工夫, 那 Redis 缓存你一定用过的吧
我: 用过的..
面试官: 那你跟我说下你是怎么用的?
我是结合 spring boot 使用的. 一般有两种方式, 一种是直接通过 RedisTemplate 来使用, 另一种是使用 spring cache 集成 Redis(也就是注解的方式). 具体的代码我就不说了, 在我的掘金中有一个 demo(见下).
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-Redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-Redis</artifactId>
- </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>
- </dependency>
- </dependencies>
- server:
- port: 8082
- servlet:
- session:
- timeout: 30ms
- spring:
- cache:
- type: Redis
- Redis:
- host: 127.0.0.1
- port: 6379
- password:
- # Redis 默认情况下有 16 个分片, 这里配置具体使用的分片, 默认为 0
- database: 0
- lettuce:
- pool:
- # 连接池最大连接数(使用负数表示没有限制), 默认 8
- max-active: 100
- public class User implements Serializable{
- private static final long serialVersionUID = 662692455422902539L;
- private Integer id;
- private String name;
- private Integer age;
- public User() {
- }
- public User(Integer id, String name, Integer age) {
- this.id = id;
- this.name = name;
- this.age = age;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- @Override
- public String toString() {
- return "User{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
- }
- @Configuration
- @AutoConfigureAfter(RedisAutoConfiguration.class)
- public class RedisCacheConfig {
- @Bean
- public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory connectionFactory) {
- RedisTemplate<String, Serializable> template = new RedisTemplate<>();
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
- template.setConnectionFactory(connectionFactory);
- return template;
- }
- }
- @RestController
- @RequestMapping("/user")
- public class UserController {
- public static Logger logger = LogManager.getLogger(UserController.class);
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private RedisTemplate<String, Serializable> redisCacheTemplate;
- @RequestMapping("/test")
- public void test() {
- redisCacheTemplate.opsForValue().set("userkey", new User(1, "张三", 25));
- User user = (User) redisCacheTemplate.opsForValue().get("userkey");
- logger.info("当前获取对象:{}", user.toString());
- }
- public interface UserService {
- User save(User user);
- void delete(int id);
- User get(Integer id);
- }
- @Service
- public class UserServiceImpl implements UserService{
- public static Logger logger = LogManager.getLogger(UserServiceImpl.class);
- private static Map<Integer, User> userMap = new HashMap<>();
- static {
- userMap.put(1, new User(1, "肖战", 25));
- userMap.put(2, new User(2, "王一博", 26));
- userMap.put(3, new User(3, "杨紫", 24));
- }
- @CachePut(value ="user", key = "#user.id")
- @Override
- public User save(User user) {
- userMap.put(user.getId(), user);
- logger.info("进入 save 方法, 当前存储对象:{}", user.toString());
- return user;
- }
- @CacheEvict(value="user", key = "#id")
- @Override
- public void delete(int id) {
- userMap.remove(id);
- logger.info("进入 delete 方法, 删除成功");
- }
- @Cacheable(value = "user", key = "#id")
- @Override
- public User get(Integer id) {
- logger.info("进入 get 方法, 当前获取对象:{}", userMap.get(id)==null?null:userMap.get(id).toString());
- return userMap.get(id);
- }
- }
- @RestController
- @RequestMapping("/user")
- public class UserController {
- public static Logger logger = LogManager.getLogger(UserController.class);
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private RedisTemplate<String, Serializable> redisCacheTemplate;
- @Autowired
- private UserService userService;
- @RequestMapping("/test")
- public void test() {
- redisCacheTemplate.opsForValue().set("userkey", new User(1, "张三", 25));
- User user = (User) redisCacheTemplate.opsForValue().get("userkey");
- logger.info("当前获取对象:{}", user.toString());
- }
- @RequestMapping("/add")
- public void add() {
- User user = userService.save(new User(4, "李现", 30));
- logger.info("添加的用户信息:{}",user.toString());
- }
- @RequestMapping("/delete")
- public void delete() {
- userService.delete(4);
- }
- @RequestMapping("/get/{id}")
- public void get(@PathVariable("id") String idStr) throws Exception{
- if (StringUtils.isBlank(idStr)) {
- throw new Exception("id 为空");
- }
- Integer id = Integer.parseInt(idStr);
- User user = userService.get(id);
- logger.info("获取的用户信息:{}",user.toString());
- }
- }
- @SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
- @EnableCaching
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
- public static String getData(String key) throws InterruptedException {
- // 从 Redis 查询数据
- String result = getDataByKV(key);
- // 参数校验
- if (StringUtils.isBlank(result)) {
- try {
- // 获得锁
- if (reenLock.tryLock()) {
- // 去数据库查询
- result = getDataByDB(key);
- // 校验
- if (StringUtils.isNotBlank(result)) {
- // 插进缓存
- setDataToKV(key, result);
- }
- } else {
- // 睡一会再拿
- Thread.sleep(100L);
- result = getData(key);
- }
- } finally {
- // 释放锁
- reenLock.unlock();
- }
- }
- return result;
- }
- appendfsync yes
- appendfsync always #每次有数据修改发生时都会写入 AOF 文件.
- appendfsync everysec #每秒钟同步一次, 该策略为 AOF 的缺省策略.
来源: http://www.bubuko.com/infodetail-3359292.html