zoukankan      html  css  js  c++  java
  • ServiceStack.Redis 中关系操作的局限与bug

    redis是文档型的,nosql中难处理的是关系。

    比如人可以发博客,博客可以有分类。按照传统sql中,用户表和分类表都是主表,博客表是从表,有用户的外键和分类的外键

    如果使用文档型的思考方式。

    为用户A(User id=1)存储他的博客,在redis中是list或set

    为分类A(Cate id=1)存储分类下的博客,在redis中是list或set

    则当用户A向分类A中添加一条新博客时,需要同时向两个list(或set)中增加数据,而且理论上应该是事务的,修改的时候也需要同时修改两个。

    这样的好处是读操作是完全优化的,直接从一个key中读出来的东西,马上就可以用

    坏处是写操作太复杂,稍不注意可能就漏掉什么东西,更新博客需要更新非常多个list中的元素。

    ServiceStack的redis客户端专门为这种情况提供了几个方法。

    先来看实体类

    public class User
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
    }
    
    public class Blog
    {
        public int Id { get; set; }
    
        public string Title { get; set; }
    }
    
    public class Cate
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    }

    很简单的3个类,用于表示用户,分类,博客3种概念

    使用强类型的client保存3个实例

    var clientsManager = new PooledRedisClientManager();
    using (IRedisClient redis = clientsManager.GetClient())
    {
        redis.FlushAll();
    
        var u = new User { Id = 1, Name = "A" };
    
        var c = new Cate { Id = 1, Name = "A" };
    
        var blog = new Blog { Id = 1, Title = "blog" };
    
        redis.As<User>().Store(u);
        redis.As<Cate>().Store(c);
        redis.As<Blog>().Store(blog);
    
    }
    image

    可以通过客户端软件查看,3个实体都保存成功,但是并没有体现关系

    redis.As<User>().StoreRelatedEntities(u.Id, blog);
    redis.As<Cate>().StoreRelatedEntities(c.Id, blog);

    之后调用保存关系的语句,as的是主表,第一个是主表主键,第二个是从对象

    image

    redis中,新建了2个key,ref:Cate/Blog:1和ref:User/Blog:1

    他们的值是一个set

    image

    set中的具体内容并不是对象本身,而是对象在urn中的key

    var blogs = redis.As<User>().GetRelatedEntities<Blog>(u.Id);

    可以通过相关语句来获取从表内容

    image

    直接取到了blog的实体

    但是在删除的时候有一个bug

    image

    他的方法指定的第二个参数是childId,所以我们传进去id,但是删除不掉

    redis.As<User>().DeleteRelatedEntity<Blog>(u.Id, blog);

    不使用id,而使用对象,也依然删除不掉

    image

    查看源码发现,当他运行从set中删除东西的时候,找key是对的,但是要被删掉的元素生成的不对

    image

    添加的时候,他拿UrnKey<T>(x)生成了实体保存的key,而删除的时候没有

    删除的时候,直接是序列化的,则1,序列化后就是1,而我们的set中,并没有1这个值,所以是没有删掉任何东西的。

    image

    client的UrnKey是个internal的方法,再次被恶心了

    redis.As<User>().DeleteRelatedEntity<Blog>(u.Id, (redis as RedisNativeClient).NamespacePrefix + IdUtils.CreateUrn(blog));

    我们只能使用这么复杂的方式,等于把他内部的代码都拿到外面来处理了,当然你可以clone他的源码去改或者写扩展方法。

    根据关系的key,我们大概可以分析出

    ref:主表/从表:主表主键值

    但这样的方式有一定的局限性,就是对同一个主从类型,他们之间只能表达一种关系。

    比如人与博客,如果我需要表达 人写的博客,人推荐的博客 这两种关系(都是人与博客的),则无法实现

    比如User 1,他写了Blog 1,推荐了Blog 2。但是他们都会被加入到ref:User/Blog:1中,无法区分是他写的还是他推荐的。

    所以我们需要为两种类型之间的关系去给一个名字,来区分到底是那种关系

    image

    在RedisTypedClient<T>中有一个GetChildReferenceSetKey方法,是来生成这个key的,private方法,再次被恶心

    当然,可以通过对NamespacePrefix设置一个不同的值来区分,但是感觉上怪怪的,因为这个在我看来是不同的应用程序,为防止key重复而设置的

    有兴趣的朋友可以写几个扩展方法,反正源码基本都能看到

    再再再次被恶心到的是,竟然github没有开放issues提交

  • 相关阅读:
    使用golang访问kubebernetes
    使用 Rancher 管理现有 Kubernetes 集群
    Running powershell scripts during nuget package installation and removal
    How to Create, Use, and Debug .NET application Crash Dumps in 2019
    寻找写代码感觉(一)之使用 Spring Boot 快速搭建项目
    Selenium+Java之解决org.openqa.selenium.InvalidArgumentException: invalid argument报错问题
    Selenium环境搭建
    关于Xpath定位方法知道这些基本够用
    Web自动化之浏览器启动
    【翻译】编写代码注释的最佳实践
  • 原文地址:https://www.cnblogs.com/czcz1024/p/4192068.html
Copyright © 2011-2022 走看看