29、从 C++ 访问 Coherence

从 C++ 访问 Coherence

1. 代码差异概述

整体而言,代码与原版差别不大。下面我们逐一分析这些差异。

2. 理解规范(Specifications)

最奇特的部分当属继承语句:

public cloneable_spec<Account>

这是对象模型的一部分,被称为基于“spec”的类定义,其中“spec”是“specification”的缩写。Spec 会注入大量样板代码,使新管理类的编写变得更加容易。例如,会注入以下内容:
- 隐式虚拟继承自 Object ,使其成为管理类。
- 定义 Account::Handle/View/Holder 嵌套智能指针类型定义。
- 定义 Account::super 类型定义指向父类。
- 添加静态 create 方法以匹配 Account 的构造函数,并返回 Account::Handle
- 添加 clone() 方法实现,委托给 Account 的拷贝构造函数。

Spec 有多种类型:
| Spec 类型 | 描述 |
| ---- | ---- |
| class_spec | 最基本的 spec,用于定义管理类 |
| cloneable_spec | 继承自 class_spec ,支持克隆 |
| abstract_spec | 定义一个不可实例化的类,具有部分实现 |
| interface_spec | 定义一个不可实例化的类,包含所有纯虚方法 |
| throwable_spec | 定义一个基于 spec 的异常类 |

所有的 spec 都会自动添加对 Object 的继承,并且各自会添加一些特定的特性。Spec 接受以下模板参数:

spec<class, extends<parent>, implements<interface, ...> >

参数说明如下:
- class :必需,指定要定义的类的名称。
- extends<parent> :可选,指定要派生的类,默认为 extends <Object> interface_spec 不包含此参数。
- implements<interface1, interface2, ...> :可选,该类实现的接口列表,默认为 implements<>

3. 工厂方法

相关的部分是:

friend class factory<Account>;

这个友元声明允许自动生成的 create 方法访问受保护的构造函数。这是 spec 自身无法注入的唯一样板代码。

4. 成员变量

数据成员声明如下:

private:
    const int64_t       m_lId;
    MemberView<String>  m_sDescription;
    MemberHandle<Money> m_balance;
    int64_t             m_lLastTransactionId;
    const int64_t       m_lCustomerId;
  • 我们将 long long 数据成员改为使用 int64_t 。在大多数现代 C++ 编译器中, long long 是 64 位整数类型,但不保证特定大小。 int64_t 是固定大小的类型,保证为 64 位宽。按照惯例,管理类型使用固定大小的原语,但这不是强制要求。
  • 我们将 std::string 替换为 Coherence 管理的 String ,通过 View 引用。 Money 类也进行了类似的修改,允许通过 Handle 引用。这样做是为了提高序列化效率,避免在序列化过程中进行类型转换。
  • 对于 String Money ,我们使用 MemberView<String> MemberHandle<Money> 而不是嵌套的 String::View Money::Handle 。这是因为嵌套的 Handle/View/Holder 智能指针类型不是线程安全的。由于存储在缓存中的对象可能会被多个线程访问,因此内部必须是线程安全的。 MemberHandle/View/Holder 是线程安全的变体,应作为数据成员引用使用。

Coherence C++ 还包含另外两种线程安全的智能指针变体:
- FinalHandle/View/Holder :不可变的智能指针,具有对象模型性能优势。
- WeakHandle/View/Holder :弱引用风格的智能指针,用于处理对象图中的循环引用。

5. 实现构造函数

构造函数如下:

protected:
    Account(int64_t lId, String::View sDesc, Money::View balance,
            int64_t lLastTransId, int64_t lCustomerId)
        : m_lId(lId), m_sDescription(self(), sDesc),
          m_balance(self(), balance),            
          m_lLastTransactionId(lLastTransId),
          m_lCustomerId(lCustomerId)
        {
        }
    Account()
       : m_lId(0), m_sDescription(self(), sDesc),
         m_balance(self(), balance), m_lLastTransactionId(0), 
         m_lCustomerId(0)
        {
        }
    Account(const Account& that)
       : m_lId(lId), m_sDescription(self(), sDesc),
         m_balance(self(), cast<Money::View>(balance->clone()),            
         m_lLastTransactionId(lLastTransId),
         m_lCustomerId(lCustomerId)
        {
        }
  • 构造函数现在声明为受保护的,这会阻止基于栈和 operator new 的分配,使静态 create 方法成为唯一的分配机制。
  • MemberView MemberHandle 使用 self() 进行初始化。线程安全的智能指针接受一个可选的第二个参数,即它们要引用的对象,如果省略则默认为 NULL self() 方法返回对正在创建的管理对象的基类的引用,概念上类似于 this 指针,但它引用的是完全初始化的基类,而 this 引用的是部分初始化的派生类型。
6. 实现方法

方法现在都声明为 virtual 。虽然这不是必需的,但通常管理类的设计类似于 Java,所有方法都是虚拟的。最后,我们重写了 Object 声明的一些标准方法,用于哈希、相等性测试和对象打印。

7. 实现 PortableObject 接口

要使类实现 PortableObject 接口,我们只需修改继承语句以表明该类实现 PortableObject ,然后实现其方法并注册类型:

class Account
    : public cloneable_spec<Account,
          extends<Object>,
          implements<PortableObject> >
    {
    ...
    // ----- PortableObject methods --------------------------------
    public:
        virtual void writeExternal(PofWriter::Handle hWriter) const
            {
            hWriter->writeInt64(0, getId());
            hWriter->writeString(1, getDescription());
            hWriter->writeObject(2, getBalance());
            hWriter->writeInt64(3, getLastTransactionId());
            hWriter->writeInt64(4, getCustomerId());
            }
        virtual void readExternal(PofReader::Handle hReader)
            {
            m_lId = hReader->readInt64(0, getId());
            setDescription(hReader->readString(1));
            setBalance(cast<Money::Handle>(hReader->readObject(2)));
            setLastTransactionId(hReader->readInt64(3));
            m_lCustomerId = hReader->readInt64(4);
            }
   };
COH_REGISTER_PORTABLE_CLASS( 
    POF_TYPE_ACCOUNT, Account); // must be in .cpp

需要注意的是,在 readExternal 中,我们需要设置两个 const 数据成员,这是不允许的。这是因为 PortableObject 反序列化发生在对象已经实例化之后。为了实现 const 正确性,我们需要要么从这些数据成员的声明中移除 const 修饰符,要么在 readExternal 中强制转换掉它。

8. 实现外部序列化器

最后一种序列化选项是为我们的管理数据对象编写外部序列化器。这里我们为非 PortableObject 版本的管理 Account 类创建一个序列化器:

class AccountSerializer
    : public class_spec<AccountSerializer,
        extends<Object>,
        implements<PofSerializer> >
    {
    friend class factory<AccountSerializer>;
    public:
     virtual void serialize(PofWriter::Handle hWriter, Object::View v) 
            const
            {
            Account::View vAccount = cast<Account::View>(v);
            hWriter->writeInt64(0, vAccount->getId());
            hWriter->writeString(1, vAccount->getDescription());
            hWriter->writeObject(2, vAccount->getBalance());
            hWriter->writeInt64(3, vAccount->getLastTransactionId());
            hWriter->writeInt64(4, vAccount->getCustomerId());

            hWriter->writeRemainder(NULL); // mark end of object
            }
     virtual Object::Holder deserialize(PofReader::Handle hReader) 
            const
            {
            int64_t       lId         = hReader->readInt64(0);
            String::View  sDesc       = hReader->readString(1);
            Money::Handle hBalance    = cast<Money::Handle>(
                hReader->readObject(2));
            int64_t       lTransId    = hReader->readInt64(3);
            int64_t       lCustomerId = hReader->readInt64(4);

            hReader->readRemainder(); // read to end of object
            return Account::create(lId, sDesc, hBalance, lTransId, 
                lCustomerId);
            }
   };
COH_REGISTER_POF_SERIALIZER(POF_TYPE_ACCOUNT,    
    TypedClass<Account>::create(),
    AccountSerializer::create()); // must be in .cpp

使用新的管理 Account 类比使用 Managed<Account> 更直接:

// construct managed key and value 
Account::Handle   hAccount = Account::create(32105, "checking",
    Money::create(7374, 10, "USD"), 55, 62409);
Integer64::Handle hlKey    = Integer64::create(32105);

// cache hAccount
hCache->put(hlKey, hAccount);
// retrieve the cached value 
Account::View vResult = cast<Account::View>(hCache->get(hlKey));
std::cout << "retrieved " << vResult << " from cache for key "
          << vResult->getId() << std::endl;
9. 执行查询

Coherence C++ 提供了一个与 Java 版本非常相似的 QueryMap 接口:

class QueryMap
    : public interface_spec<QueryMap,
        implements<Map> >
    {
    public:
        virtual Set::View keySet(Filter::View vFilter) const;
        virtual Set::View entrySet(Filter::View vFilter) const;
        virtual Set::View entrySet(Filter::View vFilter, 
            Comparator::View vComparator) const;
        virtual void addIndex(ValueExtractor::View vExtractor,
            bool fOrdered, Comparator::View vComparator);
        virtual void removeIndex(ValueExtractor::View vExtractor);
    };

执行查询的步骤如下:
1. 构造一个过滤器来确定匹配记录的条件。
2. 将过滤器提供给 keySet entrySet 方法。
3. 如果需要,可以使用 Comparator 变体对结果集进行排序。

除非在本地缓存上操作,否则提供的过滤器、提取器和比较器仅用作可序列化的存根,所有处理都将在远程 Java 缓存服务器上完成。

10. 值提取器(Value Extractors)

QueryMap 的核心概念是 ValueExtractor ,用于从缓存条目中提取嵌入式值。例如,可以使用值提取器从 Account 数据对象中提取余额。这些提取器用于表达过滤条件和对缓存应用索引以优化查询性能。

Coherence C++ 包含一个 ReflectionExtractor 实现,适用于对远程缓存进行查询,前提是缓存服务器包含数据对象的 Java 版本:

hCache->addIndex(ReflectionExtractor::create("getBalance"), false,  
    NULL);

对于本地缓存,由于 C++ 语言没有反射支持, ReflectionExtractor 在用于 C++ 本地缓存时会抛出 UnsupportedOperationExpection 。不过,Coherence C++ 提供了 TypedExtractor ,它结合了宏、模板和函数指针,能够很好地模拟 Java 的 ReflectionExtractor

ValueExtractor::View vExtractor = 
    COH_TYPED_EXTRACTOR(Money::View, Account, getBalance);

宏参数分别是提取值的类型、要执行提取的类类型以及用于获取值的常量访问器方法。对于返回非管理类型的访问器方法,有一个 BoxExtractor 变体,它会将原始类型包装回相应的管理类型。

graph TD;
    A[开始] --> B[构造过滤器];
    B --> C[选择 keySet 或 entrySet 方法];
    C --> D[提供过滤器];
    D --> E[根据需要使用 Comparator 排序];
    E --> F[获取结果集];
    F --> G[结束];

另外,还有 PofExtractor 类型,它允许在服务器端进行高效提取,而无需完全反序列化或对应的 Java 类:

ValueExtractor::View vExtractor = PofExtractor::create(typeid(Money),  
    Account::BALANCE);

在使用 PofExtractor 时,最好为属性索引创建静态标识符。在 Coherence 3.5 中,C++ PofExtractor 只能应用于远程缓存。

我们还可以实现 PropertyExtractor

#define COH_PROPERTY_EXTRACTOR(TYPE, CLASS, PROPERTY) \
  coherence::util::extractor::TypedExtractor< \
        TYPE, CLASS, &CLASS::get##PROPERTY> \
            ::create(COH_TO_STRING("get" << #PROPERTY));

ValueExtractor::View vExtractor =
    COH_PROPERTY_EXTRACTOR(Money::View, Account, Balance);

对于 Managed<> 数据对象,需要使用特殊版本:

#define COH_MANAGED_PROPERTY_EXTRACTOR(TYPE, CLASS, PROPERTY) \
    coherence::util::extractor::BoxExtractor< \
        TYPE, CLASS, &CLASS::get##PROPERTY, \
            coherence::lang::Managed<CLASS>::Holder> \
                ::create(COH_TO_STRING("get" << #PROPERTY));

ValueExtractor::View vExtractor =
    COH_MANAGED_PROPERTY_EXTRACTOR(Money::View, Account, Balance);
11. 过滤器(Filters)

Coherence C++ 提供了与 Java 相同的内置过滤器集,这些过滤器能够在本地和远程缓存上执行。当内置过滤器的组合不足以表达查询时,你可以编写自定义过滤器。除非过滤器仅针对本地缓存,否则你还需要提供一个 Java 版本,因为在远程缓存上运行时,实际的过滤操作将由 Java 版本完成。

Java 和 C++ 实现的逻辑不必完全相同,但实现必须兼容,即对于给定的一组输入,两个实现应产生相同的结果,并且两个实现的序列化形式应相同。

12. 在 C++ 中执行查询

现在我们可以将上述内容结合起来,在 C++ 中执行查询:

// add an index for the description property on the Account class
ValueExtractor::View vExtractor =
    COH_PROPERTY_EXTRACTOR(String::View, Account, Description);
hCache->addIndex(vExtractor, false, NULL);

// query for all "checking" Accounts
Set::View vSetResult =
    hCache->entrySet(LikeFilter::create(vExtractor, "checking%"));

// iterate the result set printing each matching entry
for (Iterator::Handle hIter = vSetResult->iterator();  
     hIter->hasNext(); )
    {
    Map::Entry::View vEntry = cast<Map::Entry::View>(hIter->next());
    Account::View  vAccount = cast<Account::View>(vEntry->getValue()); 
    std::cout << vAccount << std::endl;
    }

执行查询和迭代结果非常简单,真正的工作在于定义提取器和过滤器。了解如何实现自定义提取器和过滤器很重要,但要记住,使用内置的提取器和过滤器也能满足很多需求。

13. 执行聚合器和条目处理器

Coherence C++ 通过本地 InvocableMap 接口提供对聚合器和条目处理器的全面支持,该接口与 Java 版本非常相似:

class InvocableMap
    : public interface_spec<InvocableMap,
        implements<Map> >
    {
    public:
        virtual Object::Holder invoke(Object::View vKey, 
            EntryProcessor::Handle hAgent);
        virtual Map::View invokeAll(Collection::View vCollKeys, 
            EntryProcessor::Handle hAgent);
        virtual Map::View invokeAll(Filter::View vFilter, 
            EntryProcessor::Handle hAgent);

        virtual Object::Holder aggregate(Collection::View vCollKeys, 
            EntryAggregator::Handle hAgent) const;
        virtual Object::Holder aggregate(Filter::View vFilter,     
            EntryAggregator::Handle hAgent) const;
    };

该接口允许通过显式的键和过滤器选择要处理或聚合的条目。要执行的操作可以表示为 EntryProcessor Aggregator ,有许多内置实现,你也可以提供自己的自定义实现。

从 C++ 访问 Coherence

14. 综合运用示例

为了更好地理解如何在实际场景中运用上述的各种功能,我们来看一个更完整的示例。假设我们有一个账户管理系统,需要对账户信息进行存储、查询和统计。

首先,我们回顾之前定义的 Account 类,它已经实现了 PortableObject 接口,方便进行序列化和反序列化操作。

class Account
    : public cloneable_spec<Account,
          extends<Object>,
          implements<PortableObject> >
    {
    // 之前定义的成员变量、构造函数、方法等
    ...
    // ----- PortableObject methods --------------------------------
    public:
        virtual void writeExternal(PofWriter::Handle hWriter) const
            {
            hWriter->writeInt64(0, getId());
            hWriter->writeString(1, getDescription());
            hWriter->writeObject(2, getBalance());
            hWriter->writeInt64(3, getLastTransactionId());
            hWriter->writeInt64(4, getCustomerId());
            }
        virtual void readExternal(PofReader::Handle hReader)
            {
            m_lId = hReader->readInt64(0, getId());
            setDescription(hReader->readString(1));
            setBalance(cast<Money::Handle>(hReader->readObject(2)));
            setLastTransactionId(hReader->readInt64(3));
            m_lCustomerId = hReader->readInt64(4);
            }
   };
COH_REGISTER_PORTABLE_CLASS( 
    POF_TYPE_ACCOUNT, Account); // must be in .cpp

接下来,我们创建一些账户并将它们存储到缓存中:

// 创建缓存
NamedCache::Handle hCache = CacheFactory::getCache("accounts-cache");

// 创建多个账户
Account::Handle hAccount1 = Account::create(1, "checking", Money::create(1000, 0, "USD"), 1, 1001);
Account::Handle hAccount2 = Account::create(2, "savings", Money::create(5000, 0, "USD"), 2, 1002);
Account::Handle hAccount3 = Account::create(3, "checking", Money::create(2000, 0, "USD"), 3, 1001);

// 将账户存入缓存
hCache->put(Integer64::create(1), hAccount1);
hCache->put(Integer64::create(2), hAccount2);
hCache->put(Integer64::create(3), hAccount3);

现在,我们可以执行一些查询操作。例如,查询所有类型为 “checking” 的账户:

// 创建值提取器
ValueExtractor::View vExtractor = COH_PROPERTY_EXTRACTOR(String::View, Account, Description);
// 创建过滤器
Filter::View vFilter = LikeFilter::create(vExtractor, "checking");

// 执行查询
Set::View vSetResult = hCache->entrySet(vFilter);

// 遍历结果
for (Iterator::Handle hIter = vSetResult->iterator(); hIter->hasNext(); )
{
    Map::Entry::View vEntry = cast<Map::Entry::View>(hIter->next());
    Account::View vAccount = cast<Account::View>(vEntry->getValue());
    std::cout << "Found account: " << vAccount->getId() << " - " << vAccount->getDescription() << std::endl;
}

我们还可以使用聚合器来统计所有账户的总余额:

// 创建值提取器用于提取余额
ValueExtractor::View vBalanceExtractor = COH_PROPERTY_EXTRACTOR(Money::View, Account, Balance);
// 创建求和聚合器
EntryAggregator::Handle hSumAggregator = SummingAggregator::create(vBalanceExtractor);

// 执行聚合操作
Object::Holder hResult = hCache->aggregate(hCache->keySet(), hSumAggregator);
Money::View vTotalBalance = cast<Money::View>(hResult);

std::cout << "Total balance of all accounts: " << vTotalBalance->getAmount() << " " << vTotalBalance->getCurrency() << std::endl;
15. 性能优化建议

在使用 Coherence C++ 进行开发时,为了提高性能,我们可以采取以下一些建议:
- 合理使用索引 :根据查询的频率和条件,为经常查询的属性添加索引。例如,如果经常根据账户类型进行查询,就可以为账户类型属性添加索引。

ValueExtractor::View vTypeExtractor = COH_PROPERTY_EXTRACTOR(String::View, Account, Description);
hCache->addIndex(vTypeExtractor, false, NULL);
  • 选择合适的过滤器和提取器 :根据具体的查询需求,选择最适合的过滤器和提取器。对于本地缓存,优先使用 TypedExtractor 等高效的提取器;对于远程缓存,确保使用的过滤器和提取器在 Java 端有对应的实现。
  • 避免不必要的序列化和反序列化 :尽量减少对象的序列化和反序列化操作,因为这些操作会消耗一定的性能。可以通过合理设计数据结构和使用高效的序列化方式来实现。
16. 总结

从 C++ 访问 Coherence 为我们提供了强大的功能,包括对象管理、序列化、查询、聚合等。通过使用基于 spec 的类定义,我们可以简化管理类的编写;利用各种值提取器和过滤器,我们可以灵活地进行数据查询和处理;同时,通过聚合器和条目处理器,我们可以对数据进行统计和修改。

在实际开发中,我们需要根据具体的业务需求,合理选择和使用这些功能,并注意性能优化。通过不断地实践和探索,我们可以更好地发挥 Coherence C++ 的优势,开发出高效、稳定的应用程序。

以下是一个总结的流程图,展示了从创建账户到查询和聚合的主要步骤:

graph LR;
    A[创建账户] --> B[存入缓存];
    B --> C[添加索引];
    C --> D[创建过滤器和提取器];
    D --> E[执行查询];
    E --> F[遍历结果];
    D --> G[创建聚合器];
    G --> H[执行聚合操作];
    H --> I[输出聚合结果];

希望本文能够帮助你更好地理解和使用 Coherence C++,在实际项目中取得更好的效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值