微服务已成为软件开发领域的一种变革性架构方法,它提供了从单体结构到更加模块化和可扩展的系统的范式转变。微服务的核心是将复杂的应用程序分解为更小的、可独立部署的服务,这些服务可以无缝通信,从而促进敏捷性、灵活性和易于维护。这种去中心化的方法使开发人员能够专注于特定功能,从而实现快速开发、持续集成和高效扩展,以满足现代动态业务环境的需求。随着组织越来越多地接受微服务的好处,本文探讨了与这种架构风格相关的关键原则、优势和挑战,阐明了微服务在塑造软件设计和部署的未来方面的关键作用。
微服务应用程序的一个基本特征是能够利用不同的技术堆栈独立设计、开发和部署每个微服务。每个微服务都作为一个独立的自治应用程序运行,具有自己的专用持久性存储,无论是关系数据库、NoSQL 数据库,甚至是遗留文件存储系统。这种自主性使单个微服务能够独立扩展,从而促进无缝的实时基础架构调整并增强整体可管理性。
NCache 微服务架构中的缓存层
在应用程序事务激增的情况下,瓶颈可能会持续存在,尤其是在微服务将数据存储在不可扩展的关系数据库中的体系结构中。简单地部署微服务的其他实例并不能缓解问题。
为了应对这些挑战,请考虑集成 NCache 作为分布式缓存在微服务和数据存储之间的缓存层。NCache 不仅用作缓存,还用作可扩展的内存中发布者/订阅者消息传递代理,促进微服务之间的异步通信。
微服务 Java 应用程序性能优化可以通过缓存技术来实现,例如缓存项锁定、分组缓存数据、休眠缓存、SQL 查询、数据结构、spring 数据缓存技术发布-订阅消息传递等等 NCache.请检查开箱即用的功能 NCache.
运用 NCache 作为 Hibernate 二级 Java 缓存
Hibernate 一级缓存
Hibernate 一级缓存用作链接到 Session 对象的基本独立 (proc) 缓存,仅限于当前会话。尽管如此,第一级缓存的一个缺点是它无法在不同会话之间共享对象。如果多个会话需要同一个对象,则每个会话都会触发数据库行程来加载它,从而加剧数据库流量并加剧可伸缩性问题。此外,当会话结束时,所有缓存的数据都将丢失,因此需要在下次检索时从数据库中重新提取数据。
Hibernate 二级缓存
对于仅依赖第一级缓存的高流量 Hibernate 应用程序,在 Web 场中部署会带来与服务器之间的缓存同步相关的挑战。在 Web 场设置中,每个节点都运行一个 Web 服务器(如 Apache、Oracle WebLogic 等),其中包含多个 httpd 进程实例来处理请求。这些 HTTP 工作进程中的每个 Hibernate 一级缓存都维护直接从数据库缓存的相同数据的不同版本,从而带来同步问题。
这就是 Hibernate 提供具有提供程序模型的二级缓存的原因。Hibernate 二级缓存使您能够集成第三方分布式 (out-proc) 缓存提供程序,以跨会话和服务器缓存对象。与第一级缓存不同,第二级缓存与 SessionFactory 对象相关联,可供整个应用程序访问,超出单个会话。
启用 Hibernate 二级缓存会导致两个缓存共存:一级缓存和二级缓存。Hibernate 首先尝试从第一级缓存中检索对象;如果不成功,它会尝试从二级缓存中获取它们。如果两次尝试都失败,则直接从数据库加载对象并缓存。此配置大大减少了数据库流量,因为很大一部分数据由二级分布式缓存提供。
NCache Java 通过扩展 org.hibernate.cache.CacheProvider 实现了 Hibernate 二级缓存提供程序。集成 NCache Java Hibernate 分布式缓存提供程序与 Hibernate 应用程序不需要更改代码。通过这种集成,您可以将 Hibernate 应用程序扩展到多服务器配置,而不会使数据库成为瓶颈。NCache 还提供企业级分布式缓存功能,包括数据大小管理、跨服务器数据同步等。
合并 NCache Java Hibernate 缓存提供程序,只需对您的hibernate.cfg.xml和ncache.xml进行简单的修改即可。
因此,随着 NCache Java Hibernate 分布式缓存提供程序,您可以无缝地实现 Hibernate 应用程序的线性可扩展性,无需更改现有代码。
代码片段
// Configure Hibernate properties programmatically
Properties hibernateProperties = new Properties();
hibernateProperties.put("hibernate.connection.driver_class", "org.h2.Driver");
hibernateProperties.put("hibernate.connection.url", "jdbc:h2:mem:testdb");
hibernateProperties.put("hibernate.show_sql", "false");
hibernateProperties.put("hibernate.hbm2ddl.auto", "create-drop");
hibernateProperties.put("hibernate.cache.use_query_cache", "true");
hibernateProperties.put("hibernate.cache.use_second_level_cache", "true");
hibernateProperties.put("hibernate.cache.region.factory_class", "org.hibernate.cache.jcache.internal.JCacheRegionFactory");
hibernateProperties.put("hibernate.javax.cache.provider", "com.alachisoft.ncache.hibernate.jcache.HibernateNCacheCachingProvider");
// Set other Hibernate properties as needed
Configuration configuration = new Configuration()
.setProperties(hibernateProperties).addAnnotatedClass(Product.class);
Logger.getLogger("org.hibernate").setLevel(Level.OFF);
// Build the ServiceRegistry
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties()).build();
// Build the SessionFactory
SessionFactory factory = configuration.buildSessionFactory(serviceRegistry);
// Create a List of Product objects
ArrayList<Product> products = (ArrayList<Product>) getProducts();
// Open a new Hibernate session to save products to the database. This also caches it
try (Session session = factory.openSession()) {
Transaction transaction = session.beginTransaction();
// save() method saves products to the database and caches it too
System.out.println("ProductID, Name, Price, Category");
for (Product product : products) {
System.out.println("- " + product.getProductID() + ", " + product.getName() + ", " + product.getPrice() + ", " + product.getCategory());
session.save(product);
}
transaction.commit();
System.out.println();
// Now open a new session to fetch products from the DB.
// But, these products are actually fetched from the cache
try (Session session = factory.openSession()) {
List<Product> productList = (List<Product>) session.createQuery("from Product").list();
if (productList != null) {
printProductDetails(productList);
}
}
集成 NCache 与 Hibernate 毫不费力地缓存查询结果。当 Hibernate 随后获取这些对象时,将从缓存中检索它们,从而避免了对数据库的昂贵访问。
从上面的代码示例中,产品保存在数据库中,并且还缓存;现在,当新会话打开以获取产品详细信息时,它将从缓存中获取并避免不必要的数据库访问。
了解有关 Hibernate 缓存的更多信息
扩展 NCache 发布/订阅消息传递
NCache 是专为 .NET 设计的分布式内存缓存解决方案。它的兼容性通过本机客户端和第三方集成扩展到 Java,确保对这两个平台的无缝支持。
NCache 作为为 .NET 和 Java 量身定制的内存分布式数据存储,为事件驱动的通信提供功能丰富的内存发布/订阅机制。这使得设置变得简单 NCache 作为消息传递代理,采用 Pub/Sub 模型在微服务之间进行无缝异步通信。
运用 NCache In-Memory Pub/Sub 用于微服务
NCache 通过建立微服务可以发布和订阅事件的主题来实现 Pub/Sub 功能。这些事件发布到 NCache 微服务外部的消息代理。在每个订阅微服务中,都存在一个事件处理程序,用于在原始微服务发布相应事件后对其进行管理。
在 Java 微服务领域, NCache 充当事件总线或消息代理,促进将消息中继到一个或多个订阅者。
在需要通信渠道的 Pub/Sub 模型的背景下, NCache 作为主题的媒介。这需要发布者将消息分派到指定主题,而订阅者则通过同一主题接收通知。使用 NCache 作为主题的媒介促进了模型内的松散耦合,为分布式主题提供了增强的抽象和额外的优势。
发布
下面的代码片段初始化 messageService 对象使用 NCache MessagingService 包。
初始化主题
// Create a Topic in NCache.
MessagingService messagingService = cache.getMessagingService();
Topic topic = messagingService.createTopic(topicName);
// Create a thread pool for publishers
ExecutorService publisherThreadPool = Executors.newFixedThreadPool(2);
The below code snippet used to define register the subscribers to this topic
Register subscribers to this Topic
MessageReceivedListener subscriptionListener1 = new MessageReceivedListener() {
@Override
public void onMessageReceived(Object o, MessageEventArgs messageEventArgs) {
messageReceivedSubscription1(messageEventArgs.getMessage());
}
};
MessageReceivedListener subscriptionListener2 = new MessageReceivedListener() {
@Override
public void onMessageReceived(Object o, MessageEventArgs messageEventArgs) {
messageReceivedSubscription2(messageEventArgs.getMessage());
}
};
TopicSubscription subscription1 = topic.createSubscription(subscriptionListener1);
TopicSubscription subscription2 = topic.createSubscription(subscriptionListener2);
NCache 提供两种持久订阅变体,以满足 Java 微服务中的消息持久性需求:
共享持久订阅:这允许多个订阅者连接到单个订阅。循环方法用于在各个订阅者之间分发消息。即使订阅者退出网络,消息也会持续在活动订阅者之间流动。
独占持久订阅:在此类型中,在任何给定时间,订阅只允许一个活动订阅者。在现有连接处于活动状态之前,不会接受同一订阅的新订阅者请求。
了解更多 Pub/Sub Messaging with NCache 实施在这里 缓存中的发布/订阅消息传递:概述
缓存上的 SQL 查询
NCache 为您的微服务提供了对索引缓存数据执行类似 SQL 的查询的能力。当存储所需信息的键的值未知时,此功能变得特别有用。它抽象了许多较低级别的缓存 API 调用,有助于生成更清晰、更易于维护的应用程序代码。此功能对于发现类似 SQL 的命令更直观、更舒适的人来说特别有利。
NCache 提供通过类似于 SQL 的查询搜索和删除缓存数据的功能 SELECT 和 DELETE 语句。但是,INSERT 和 UPDATE 等操作不可用。为了在缓存中执行 SELECT 查询, NCache 利用 ExecuteReader;ExecuteScalar 函数用于执行查询并从生成的数据集中检索第一行的第一列,而忽略任何额外的列或行。
为了 NCache SQL 查询要发挥作用,必须在所有正在搜索的对象上建立索引。这可以通过两种方法实现:配置缓存或利用带有“自定义属性”的代码来注释对象字段。将对象添加到缓存中时,此方法会自动在指定字段上创建索引。
代码片段
String cacheName = "demoCache";
// Connect to the cache and return a cache handle
Cache cache = CacheManager.getCache(cacheName);
// Adds all the products to the cache. This automatically creates indexes on various
// attributes of Product object by using "Custom Attributes".
addSampleData(cache);
// $VALUE$ keyword means the entire object instead of individual attributes that are also possible
String sql = "SELECT $VALUE$ FROM com.alachisoft.ncache.samples.Product WHERE category IN (?, ?) AND price < ?";
QueryCommand sqlCommand = new QueryCommand(sql);
List<String> catParamList = new ArrayList<>(Arrays.asList(("Electronics"), ("Stationery")));
sqlCommand.getParameters().put("category", catParamList);
sqlCommand.getParameters().put("price", 2000);
// ExecuteReader returns ICacheReader with the query resultset
CacheReader resultSet = cache.getSearchService().executeReader(sqlCommand);
List<Product> fetchedProducts = new ArrayList<>();
if (resultSet.getFieldCount() > 0) {
while (resultSet.read()) {
// getValue() with $VALUE$ keyword returns the entire object instead of just one column
fetchedProducts.add(resultSet.getValue("$VALUE$", Product.class));
}
}
printProducts(fetchedProducts);
利用 SQL 在 NCache 通过关注对象属性和标签来对缓存数据执行查询,而不是仅仅依赖键。
在此示例中,我们利用“自定义属性”在 Product 对象上生成索引。
了解有关 SQL 的更多信息 NCache 在 Java 中使用 SQL 查询缓存中的数据
直通读和直写
利用数据源提供程序功能 NCache 将其定位为微服务架构中数据访问的主要接口。当微服务需要数据时,它应该首先查询缓存。如果数据存在,缓存将直接提供该数据。否则,缓存将使用直读处理程序代表客户端从数据存储中获取数据,对其进行缓存,然后将其提供给微服务。
以类似的方式,对于写入操作(例如添加、更新、删除),微服务可以对缓存执行这些操作。然后,缓存使用直写处理程序自动在数据存储上执行相应的写入操作。
此外,您还可以选择强制缓存直接从数据存储中提取数据,而不管缓存中是否存在可能已过时的版本。当微服务需要最新信息并补充前面提到的缓存一致性策略时,此功能是必不可少的。
数据源提供程序功能的集成不仅简化了您的应用程序代码,而且与 NCache 的数据库同步功能,确保缓存始终使用新数据进行更新以进行处理。
ReadThruProvider
为了实现 Read-Through 缓存,有必要在 Java 中创建 ReadThruProvider 接口的实现
下面是一个代码片段,用于开始在微服务中实现 Read-Thru:
ReadThruOptions readThruOptions = new ReadThruOptions(ReadMode.ReadThru, _readThruProviderName);
product = _cache.get(_productId, readThruOptions, Product.class);
在此处阅读有关通读实现的更多信息:通读提供程序配置和实现
WriteThruProvider:
为了实现直写缓存,有必要在 Java 中创建 WriteThruProvider 接口的实现
开始在微服务中实现 Write-Thru 的代码片段:
_product = new Product();
WriteThruOptions writeThruOptions = new WriteThruOptions(WriteMode.WriteThru, _writeThruProviderName)
CacheItem cacheItem= new CacheItem(_customer)
_cache.insert(_product.getProductID(), cacheItem, writeThruOptions);
在此处阅读有关直写实现的更多信息:直写提供程序配置和实现
总结
微服务被设计为自主的,支持从其他微服务独立开发、测试和部署。虽然微服务在可伸缩性和快速开发周期方面具有优势,但应用程序堆栈的某些组件可能会带来挑战。其中一个挑战是使用关系数据库,它可能不支持必要的横向扩展来处理不断增长的负载。这就是分布式缓存解决方案的地方,例如 NCache 变得有价值。
在本文中,我们看到了各种现成的功能,例如发布/订阅消息传递、数据缓存、SQL 查询、Read-Thru 和 Write-Thru,以及 Hibernate 二级 Java 缓存技术 NCache 简化和简化数据缓存与微服务应用程序的集成,使其成为轻松自然的扩展。