redis分布式缓存(十五)一一高可用的用户注册解决方案

高可用的用户注册解决方案

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

一、redis中的Hash结构

字典

  • Redis 的字典底层实现是基于 Hash table, 一个Hash table 里面可以有多个哈希表节点,而每个哈希表节点保存了字典中的一个键值对。
  • Redis 的 Hash 和 Java 的 HashMap的内部结构实现是一致,即数组+链表结构。只是 它们的 reHash 方式不一样。

image.png

ziplist算法

Redis hash在存储优化上Hash有优化算法,称为ziplist(Redis 2.6之前称为zipmap)。

该压缩算法能够优化节省内存,ziplist也用于优化排序set和list集合的存储。当一个hash压偏到这样一个压缩过的集合中时,类似这样排列:

[key1, value1, key2, value2, ...]

带有一些key的Hash能够被打包进入线性数组结构,同时保证get和set时的O(1)性能。

这种方式不会随着hash字段增加时能够保持保证get和set时的O(1)性能,当hash增长时,它会被转为标准的字典结构来维持O(1)性能,而空间节省特性就会失效,Redis配置参数会控制这个转换:

  • list-max-ziplist-entries :默认 (512): 如果hash增长大于这个限制值,将改变到标准模式(字典)
  • list-max-ziplist-value :默认 (64): 当hash中最大元素超过这个限制值,将改变到标准模式(字典)

为了节约内存,推荐使用hash替代普通字符串的使用,这就能利用Redis强大的hash特性,因此,请尽可能使用hash,小的hash将被编码使用小的空间,

Redis Hash自然适合存储这些对象:会话session, 用户信息、商品信息、配置信息等,同时这也是缓存大量数据的选择。

二、redis中的String和Hash区别

String 适合存储用户信息,而 Hash 结构也可以存储用户信息,不过是对每个字段单独存储,因此可以在查询时获取部分字段的信息,节省网络流量。

  • 适合用 String 存储的情况:
    • 每次需要访问大量的字段
    • 存储的结构具有多层嵌套的时候
  • 适合用 Hash 存储的情况:
    • 存储海量缓存信息,使用hash结构使用内存比string结构少
    • 在大多数情况中只需要访问少量字段
    • 自己始终知道哪些字段可用,防止使用 mget 时获取不到想要的数据
  • 储存java bean对象业务剖析

三、注册微博的redis技术方案

注册

存储

存储方式

用户

DB

redis

Hash结构

image.png

注:每个hash可以存储2的(32-1)方的键值对(40多亿)

四、SpringBoot+Redis 实现微博注册

步骤1:创建user表

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(50) NOT NULL DEFAULT '' COMMENT '密码',
  `sex` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
  `deleted` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';

SET FOREIGN_KEY_CHECKS = 1;

复制代码

步骤2:注册逻辑


@Api(description = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @ApiOperation(value="微博注册")
    @PostMapping(value = "/createUser")
    public void createUser(@RequestBody UserVO userVO) {
        User user=new User();
        BeanUtils.copyProperties(userVO,user);
        //注册
        userService.createUser(user);
    }
}

复制代码
/**
 * 微博注册
 */
public void createUser(User obj) {
    //步骤1:先入库
    this.userMapper.insertSelective(obj);
    //步骤2:入库成功后,写入redis
    obj = this.userMapper.selectByPrimaryKey(obj.getId());
    //将Object对象里面的属性和值转化成Map对象
    Map<String, Object> map = ObjectUtil.objectToMap(obj);
    //设置缓存key
    String key = Constants.CACHE_KEY_USER + obj.getId();

    //微博用户的存储采用reids的hash
    HashOperations<String, String, Object> opsForHash = redisTemplate.opsForHash();
    opsForHash.putAll(key, map);

    //步骤3:设置过期30天
    this.redisTemplate.expire(key, 30, TimeUnit.DAYS);
}
复制代码

注:这里探讨redis的hash结构的使用场景,至于高可用高并发的代码并没有仔细考虑,大家可以用lua脚本代替多次redis网络请求等。。。

redis分布式缓存系列