Today’s sharing started, please give us more advice ~

Today I’m going to introduce you to Java caching and dynamic proxies.

Caching can make the original slowly opened page become “second open”, usually visit APP and website almost all involve the use of caching.

The proxy pattern provides a proxy for other objects to control access to that object. In some cases, an object is inappropriate or cannot directly reference another object, and a proxy object can act as an intermediary between the client and the target object.

This article will share with you how to understand cache and dynamic proxy, and their application ideas, hopefully enlightening you.

The cache

One, foreword

What does caching do, besides speed up data access?

On the other hand, as everything has two sides, how can we maximize the benefits of caching while avoiding its drawbacks?

What can cache do?

As mentioned earlier, the most common understanding is that when we encounter a slow page, we want to introduce caching so that the page opens quickly. In fact, fast and slow are relative. From a technical point of view, the cache is fast because the cache is built based on memory, and the read and write speed of memory is many times faster than that of hard disk. Therefore, using memory instead of disk as the read and write medium can greatly improve the data access speed.

The process goes something like this: it speeds up access by storing the data in memory for subsequent access.

In addition, there are two other important uses of caching: prefetch and deferred write.

Three, prefetch

Prefetch is to read the data to be loaded in advance, also known as “prestorage preheating”. It is used in the system to load part of the hard disk into the memory before providing external services.

Why do you do that? Because some systems once started will face tens of thousands of requests to come in, if you directly let these requests hit the database, very large may be the database pressure explosion, directly anti-raking down, unable to respond normally.

To alleviate this problem, it is necessary to solve it through “prefetch”.

Maybe you can play it, even with the cache? That’s where scaling and load balancing come in. That’s not the topic of this article, but I’ll share it with you when I get the chance.

If “prefetch” is a buffer in front of the “data exit”, then the following “write delay” is a buffer behind the “data entry”.

Delay writing

As you probably know, writing to a database is slower than reading because there are a number of mechanisms to ensure that data is accurate when writing. So, if you want to improve the speed of writing, you can either do a separate table, or you can cache a buffer, and then write to disk in batches to speed up.

Because of the huge side effects of cross-table operation and multi-condition combination query, it is more complicated than cache, so we should give priority to cache.

Then, the caching mechanism to accelerate the “write” process can be called “write delay”, which is to write the data to the disk or database in advance, and then return success, and then periodically write the data in the memory to the disk in batches.

You may think that writing to memory is considered successful, in case of an accident, power failure, downtime and other abnormal termination of the program, the data will not be lost?

Yes, so “write late” is generally only used in scenarios where data integrity requirements are not so severe, such as the number of likes, the number of participants, etc., and can greatly relieve the pressure of frequent changes to the database.

In fact, the default persistence mechanism used in Redis, which is known as distributed cache, RDB, is also in this way.

In a mature system, there are different places where caching can be used. Here’s a look at where we can add “cache.”

Five, where can add cache?

Before we say where we can cache, let’s be clear about one thing: what do we cache? That is, what characteristics of the data need to be cached? After all, caching is an additional cost and is worth it.

In general you can use two criteria:

Hotspot data: frequently accessed, such as dozens of times per second.

Static data: rarely changes, reads more than writes.

Then you can find the right place to cache them.

Caching is by nature a “defensive” mechanism, and the flow of data between systems is an orderly process, so choosing where to put a cache is like choosing where to put a roadblock on a road. The road behind this barricade is protected from traffic.

In this way, starting from the end user and ending from the database used by the system, the locations that can be used as cache points are roughly as follows:

Each set point blocks some traffic, creating a funnel that protects the underlying system and ultimately the database.

Below is a brief description of each application scenario and the points to be aware of.

6. Cache categories

1. Browser cache

This is the closest place to the user that can be used as a cache, and with the help of the user’s “resources” (the cached data is on the user’s terminal device), the best cost performance, let the user help you share the pressure.

When you open developer tools in your browser and see “From Cache” or “From Memory Cache” or “From Disk Cache”, it means that the data has been cached on the user’s terminal and can be accessed even when the Internet is offline for this reason.

This process is done by the browser for us and is usually used to Cache images, JS, and CSS. We can Control it through cache-Control in Http headers, but we won’t go into details here. In addition, js global variables, cookies and other applications also belong to this category.

The browser cache is a cache point on the user side, so we have less control over it, and you can’t actively update data without making a new request.

CDN cache

Service providers providing CDN services deploy a large number of server nodes (which can be called “edge servers”) nationwide or even globally. Distributing data to these widely distributed servers as a cache, allowing users to access cached data on nearby servers, can help spread the load and speed it up. This works especially well on TOC-type systems.

However, it should be noted that due to the large number of nodes, updating cached data is slow, usually at least at the minute level, so it is generally only applicable to static data that does not change frequently.

The solution is to add an increment or unique identifier to the URL, such as? V = 1001. Because different urls are treated as “new” data and files, they are recreated.

Gateway (proxy) cache

A lot of times we add a layer of gateways in front of the source station for some kind of security mechanism or as an entry point for a unified triage policy.

It’s also a good place to do caching because the gateway is “business independent” and can block requests to the benefit of the source site behind it, saving a lot of CPU.

Common gateway caches are Varnish, Squid and Ngnix. In general, for simple cache application scenarios, Ngnix can be used, because most of the time we will use it for load balancing, which will reduce one technology and one complexity. For a large number of small files, you can use Varnish, whereas Squid is larger, more comprehensive and more expensive to use.

4. In-process caching

This is probably where most of us programmers first deliberately use caching.

The fact that a request can make it this far indicates that it is “business relevant” and needs to be evaluated by business logic.

Because of this, the cost of introducing a cache from here is much higher than the previous three because of the higher requirement for “data consistency” between the cache and the database.

5. Out-of-process caching

You can even write your own program to store cached data that can be called remotely by other programs.

A few more thoughts on Redis versus Memcached.

You can use Memcached if you are serious about resource utilization (CPU, memory, etc.), but your program needs to tolerate data loss because of the pure memory mechanism. If you can’t tolerate this and are aggressive about resource utilization, you can use Redis. Redis also has a larger database structure. Memcached only has key-value and is more like a NoSQL store.

6. Database cache

The database itself is its own cache module, otherwise it would not be called a memory killer, basically you give it how much memory it can eat. The database cache is the internal mechanism of the database and is usually given a configuration to set the size of the cache space for you to intervene.

Finally, the disk itself has a cache. So you’ll find that it’s really hard to get the data to write smoothly to the physical disk.

Is caching a silver bullet?

You might think that caching is so good, that more should be better, as long as it’s slow to cache?

No matter how good something looks, it has a negative side, and caching also has a number of side effects to consider. In addition to the “cache update” and “cache/data consistency” issues mentioned above, there are other issues such as the following:

1. Cache avalanche

When a large number of concurrent requests enter the database, the expected buffer effect is not achieved for some reason, even if it is only for a short period of time. As a result, all the requests are forwarded to the database, causing the database to be overloaded. This can be solved by “locking queuing” or “increasing the cache time by random value”.

2. Cache penetration

This is similar to a cache avalanche, except that it lasts longer because the data source cannot be loaded into the cache after each cache miss, resulting in a continuous cache miss. This can be resolved by “bloem filters” or “caching empty objects”.

3. Cache concurrency

If data under a cache key is set at the same time, how can service accuracy be ensured? Plus the database? What about in-process caches, out-of-process caches, and databases? The recommended solution in one sentence is to use the “DB first, cache later” approach, and cache operations with DELETE instead of set.

4. Cache bottomless pit

Although distributed caches can scale horizontally indefinitely, is the more nodes under a cluster really the better? Of course not. Caching is subject to diminishing marginal utility.

5. Cache obsolescence

Memory is always limited. If there is a large amount of data, it is essential to customize reasonable elimination strategies according to specific scenarios, such as LRU, LFU and FIFO, etc.

Java dynamic proxy

I. Agent mode

A well-known example of a proxy pattern is a reference counting pointer object.

When multiple copies of a complex object must exist, the proxy pattern can be combined with the share pattern to reduce the amount of storage. The typical approach is to create a complex object with multiple agents, each referencing the original complex object. Operations performed on the agent are forwarded to the original object. Once all agents are gone, the complex object is removed.

Second, the composition of

Abstract roles: Business methods implemented by real roles are declared through interfaces or abstract classes.

Agent role: Implements abstract role, is the agent of real role, implements abstract method through the business logic method of real role, and can attach its own operations.

Real role: Implements the abstract role and defines the business logic to be implemented by the real role for the proxy role to invoke.

Three advantages,

1. Clear responsibilities

The real role is to implement the actual business logic, not to worry about other things that are not part of the responsibility, to do a complete transaction through a late agent, with the consequences of clean programming.

2. Protected objects

A proxy object can act as an intermediary between the client and the target object, thus acting as a mediator and protecting the target object.

3. High scalability

4. Pattern structure

One is the real object you want to access (the target class), and one is the proxy object, the real object and the proxy

Object implements the same interface, accessing the proxy class first and then the actual object.

The proxy mode includes static proxy and dynamic proxy.

Static proxies are created by programmers or tools that generate source code for proxy classes and then compile them. Static means that the bytecode file of the proxy class exists before the program runs, and the relationship between the proxy class and the delegate class is determined before the program runs.

Dynamic proxies do not care about the proxy class in the implementation phase, but specify which objects in the run phase.

Static proxy

Create an interface, and then create proxied classes that implement that interface and implement abstract methods in that interface. Then create a proxy class that also implements this interface. Holds a reference to a proxied object in a proxy class, and then calls its methods in a proxy class method.

It is easy to broker a class using a static proxy. However, the disadvantages of static proxy are also exposed: since the proxy can only serve one class, if there are many classes that need to be proxy, then we need to write a large number of proxy classes, which is tedious.

Dynamic proxy

1. Dynamic proxy flow chart

2. Dynamic proxy code implementation

(1) Proxy class

Use reflection to create proxy classes at run time. Interface, proxied class unchanged, we build a ProxyInvocationHandler class to implement the InvocationHandler interface.

package com.guor.aop.dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyInvocationHandler implements InvocationHandler { private Object target; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; Public Object getProxy() {return proxy.newproxyInstance (this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // Handle the proxy instance, Public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {log(method.getName()); Object result = method.invoke(target, args); // Invoke Object result = method.invoke(target, args); return result; } public void log(String MSG) {system.out.println (" + MSG +"); }}Copy the code

The static method newProxyInstance of the Proxy class returns a Proxy instance of an interface. For each agent class, the corresponding agent controller InvocationHandler is passed.

(2) The proxied class UserService

(3) Dynamic proxy execution

(4) Console output

Seventh, dynamic proxy bottom implementation

1. Specific steps of dynamic proxy

Create your own call handler by implementing the InvocationHandler interface;

Create a dynamic Proxy class by specifying a ClassLoader object and a set of interfaces for the Proxy class;

The constructor of the dynamic proxy class, whose only parameter type is the calling processor interface type, is obtained through reflection.

A dynamic proxy class instance is created through a constructor that calls the processor object passed in as a parameter.

2. Source code analysis

NewProxyInstance (1)

Since the Proxy object is generated using the static newProxyInstance of the Proxy class, let’s go to its source code and see what it does.

(2) getProxyClass0

Use getProxyClass0(loader, INTFS) to generate a ProxyClass object.

(3) the ProxyClassFactory

The ProxyClassFactory inner class creates and defines a proxy class that returns the proxy class for the given ClassLoader and interfaces.

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<? > intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<? > interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass ! = intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) ! = null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<? > intf : interfaces) { int flags = intf.getModifiers(); if (! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); }}}Copy the code

(4) generateProxyClass

After everything checked out, call ProxyGenerator. GenerateProxyClass to generate bytecode file.

(5) generateClassFile

GenerateClassFile method to generate proxy class bytecode files:

After the bytecode is generated, defineClass0 is called to parse the bytecode, generating a Proxy Class object. After you understand the dynamic proxy class generation process, what the produced proxy class looks like and who executes it.

. Among them, the ProxyGenerator generateProxyClass saveGeneratedFiles are defined as follows in the function, it refers to whether or not to save the generated proxy class class file, the default false is not saved.

In the previous example, we modified this system variable:

System.getProperties().setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);

Summary:

This paper introduces the use of three kinds of way of thinking of the cache, then the comb in a complete system can set up the cache in several places, and share about browser, CDN and gateway (agents), cache some practical experience, did not elaborate on details, just hope we have a more systematic understanding of the cache, the hope can let us become more comprehensive.

Today’s share has ended, please forgive and give advice!