自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器、Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行、园友们热情的反馈和若干个优化建议,于是利用闲暇时间,打算对原来NettyRPC中不合理的模块进行重构,并且增强了一些特性,主要的优化点如下:
现在重点整理一下重构思路、经验,记录下来。对应源代码代码,大家可以查看我的开源github:https://github.com/tang-jie/NettyRPC 项目中的NettyRPC 2.0目录。
在最早的NettyRPC消息编解码插件中,我使用的是:JDK原生的对象序列化(ObjectOutputStream/ObjectInputStream)、Kryo、Hessian这三种方式,后续有园友向我提议,可以引入Protostuff序列化方式。经过查阅网络的相关资料,Protostuff基于Google protobuf,但是提供了更多的功能和更简易的用法。原生的protobuff是需要数据结构的预编译过程,需要编写.proto格式的配置文件,再通过protobuf提供的工具翻译成目标语言代码,而Protostuff则省略了这个预编译的过程。以下是Java主流序列化框架的性能测试结果(图片来自网络):
可以发现,Protostuff序列化确实是一种很高效的序列化框架,相比起其他主流的序列化、反序列化框架,其序列化性能可见一斑。如果用它来进行RPC消息的编码、解码工作,再合适不过了。现在贴出具体的Protostuff序列化编解码器的实现代码。
首先是定义Schema,这个是因为Protostuff-Runtime实现了无需预编译对java bean进行protobuf序列化/反序列化的能力。我们可以把运行时的Schema缓存起来,提高序列化性能。具体实现类SchemaCache代码如下:
package com.newlandframework.rpc.serialize.protostuff; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.concurrent.ExecutionException; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; /** * @author tangjie<https://github.com/tang-jie> * @filename:SchemaCache.java * @description:SchemaCache功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class SchemaCache { private static class SchemaCacheHolder { private static SchemaCache cache = new SchemaCache(); } public static SchemaCache getInstance() { return SchemaCacheHolder.cache; } private Cache, Schema> cache = CacheBuilder.newBuilder() .maximumSize(1024).expireAfterWrite(1, TimeUnit.HOURS) .build(); private Schema get(final Class cls, Cache, Schema> cache) { try { return cache.get(cls, new Callable>() { public RuntimeSchema call() throws Exception { return RuntimeSchema.createFrom(cls); } }); } catch (ExecutionException e) { return null; } } public Schema get(final Class cls) { return get(cls, cache); } }
然后定义真正的Protostuff序列化、反序列化类,它实现了RpcSerialize接口的方法:
package com.newlandframework.rpc.serialize.protostuff; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import java.io.InputStream; import java.io.OutputStream; import com.newlandframework.rpc.model.MessageRequest; import com.newlandframework.rpc.model.MessageResponse; import com.newlandframework.rpc.serialize.RpcSerialize; import org.objenesis.Objenesis; import org.objenesis.ObjenesisStd; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffSerialize.java * @description:ProtostuffSerialize功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffSerialize implements RpcSerialize { private static SchemaCache cachedSchema = SchemaCache.getInstance(); private static Objenesis objenesis = new ObjenesisStd(true); private boolean rpcDirect = false; public boolean isRpcDirect() { return rpcDirect; } public void setRpcDirect(boolean rpcDirect) { this.rpcDirect = rpcDirect; } private static Schema getSchema(Class cls) { return (Schema) cachedSchema.get(cls); } public Object deserialize(InputStream input) { try { Class cls = isRpcDirect() ? MessageRequest.class : MessageResponse.class; Object message = (Object) objenesis.newInstance(cls); Schema schema = getSchema(cls); ProtostuffIOUtil.mergeFrom(input, message, schema); return message; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } public void serialize(OutputStream output, Object object) { Class cls = (Class) object.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema schema = getSchema(cls); ProtostuffIOUtil.writeTo(output, object, schema, buffer); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } } 同样为了提高Protostuff序列化/反序列化类的利用效率,我们可以对其进行池化处理,而不要频繁的创建、销毁对象。现在给出Protostuff池化处理类:ProtostuffSerializeFactory、ProtostuffSerializePool的实现代码: package com.newlandframework.rpc.serialize.protostuff; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffSerializeFactory.java * @description:ProtostuffSerializeFactory功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffSerializeFactory extends BasePooledObjectFactory { public ProtostuffSerialize create() throws Exception { return createProtostuff(); } public PooledObject wrap(ProtostuffSerialize hessian) { return new DefaultPooledObject(hessian); } private ProtostuffSerialize createProtostuff() { return new ProtostuffSerialize(); } } package com.newlandframework.rpc.serialize.protostuff; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffSerializePool.java * @description:ProtostuffSerializePool功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffSerializePool { private GenericObjectPool ProtostuffPool; volatile private static ProtostuffSerializePool poolFactory = null; private ProtostuffSerializePool() { ProtostuffPool = new GenericObjectPool(new ProtostuffSerializeFactory()); } public static ProtostuffSerializePool getProtostuffPoolInstance() { if (poolFactory == null) { synchronized (ProtostuffSerializePool.class) { if (poolFactory == null) { poolFactory = new ProtostuffSerializePool(); } } } return poolFactory; } public ProtostuffSerializePool(final int maxTotal, final int minIdle, final long maxWaitMillis, final long minEvictableIdleTimeMillis) { ProtostuffPool = new GenericObjectPool(new ProtostuffSerializeFactory()); GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMinIdle(minIdle); config.setMaxWaitMillis(maxWaitMillis); config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); ProtostuffPool.setConfig(config); } public ProtostuffSerialize borrow() { try { return getProtostuffPool().borrowObject(); } catch (final Exception ex) { ex.printStackTrace(); return null; } } public void restore(final ProtostuffSerialize object) { getProtostuffPool().returnObject(object); } public GenericObjectPool getProtostuffPool() { return ProtostuffPool; } } 现在有了Protostuff池化处理类,我们就通过它来实现NettyRPC的编码、解码接口,达到对RPC消息编码、解码的目的。首先是Protostuff方式实现的RPC解码器代码: package com.newlandframework.rpc.serialize.protostuff; import com.newlandframework.rpc.serialize.MessageCodecUtil; import com.newlandframework.rpc.serialize.MessageDecoder; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffDecoder.java * @description:ProtostuffDecoder功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffDecoder extends MessageDecoder { public ProtostuffDecoder(MessageCodecUtil util) { super(util); } } 然后是Protostuff方式实现的RPC编码器代码: package com.newlandframework.rpc.serialize.protostuff; import com.newlandframework.rpc.serialize.MessageCodecUtil; import com.newlandframework.rpc.serialize.MessageEncoder; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffEncoder.java * @description:ProtostuffEncoder功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffEncoder extends MessageEncoder { public ProtostuffEncoder(MessageCodecUtil util) { super(util); } } 最后重构出Protostuff方式的RPC编码、解码器工具类ProtostuffCodecUtil的实现代码: package com.newlandframework.rpc.serialize.protostuff; import com.google.common.io.Closer; import com.newlandframework.rpc.serialize.MessageCodecUtil; import io.netty.buffer.ByteBuf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffCodecUtil.java * @description:ProtostuffCodecUtil功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffCodecUtil implements MessageCodecUtil { private static Closer closer = Closer.create(); private ProtostuffSerializePool pool = ProtostuffSerializePool.getProtostuffPoolInstance(); private boolean rpcDirect = false; public boolean isRpcDirect() { return rpcDirect; } public void setRpcDirect(boolean rpcDirect) { this.rpcDirect = rpcDirect; } public void encode(final ByteBuf out, final Object message) throws IOException { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); closer.register(byteArrayOutputStream); ProtostuffSerialize protostuffSerialization = pool.borrow(); protostuffSerialization.serialize(byteArrayOutputStream, message); byte[] body = byteArrayOutputStream.toByteArray(); int dataLength = body.length; out.writeInt(dataLength); out.writeBytes(body); pool.restore(protostuffSerialization); } finally { closer.close(); } } public Object decode(byte[] body) throws IOException { try { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); closer.register(byteArrayInputStream); ProtostuffSerialize protostuffSerialization = pool.borrow(); protostuffSerialization.setRpcDirect(rpcDirect); Object obj = protostuffSerialization.deserialize(byteArrayInputStream); pool.restore(protostuffSerialization); return obj; } finally { closer.close(); } } } 这样就使得NettyRPC的消息序列化又多了一种方式,进一步增强了其RPC消息网络传输的能力。 其次是优化了NettyRPC服务端的线程模型,使得RPC消息处理线程池对任务的队列容器的支持更加多样。具体RPC异步处理线程池RpcThreadPool的代码如下: package com.newlandframework.rpc.parallel; import com.newlandframework.rpc.core.RpcSystemConfig; import com.newlandframework.rpc.parallel.policy.AbortPolicy; import com.newlandframework.rpc.parallel.policy.BlockingPolicy; import com.newlandframework.rpc.parallel.policy.CallerRunsPolicy; import com.newlandframework.rpc.parallel.policy.DiscardedPolicy; import com.newlandframework.rpc.parallel.policy.RejectedPolicy; import com.newlandframework.rpc.parallel.policy.RejectedPolicyType; import java.util.concurrent.Executor; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.RejectedExecutionHandler; /** * @author tangjie<https://github.com/tang-jie> * @filename:RpcThreadPool.java * @description:RpcThreadPool功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class RpcThreadPool { private static RejectedExecutionHandler createPolicy() { RejectedPolicyType rejectedPolicyType = RejectedPolicyType.fromString(System.getProperty(RpcSystemConfig.SystemPropertyThreadPoolRejectedPolicyAttr, "AbortPolicy")); switch (rejectedPolicyType) { case BLOCKING_POLICY: return new BlockingPolicy(); case CALLER_RUNS_POLICY: return new CallerRunsPolicy(); case ABORT_POLICY: return new AbortPolicy(); case REJECTED_POLICY: return new RejectedPolicy(); case DISCARDED_POLICY: return new DiscardedPolicy(); } return null; } private static BlockingQueue createBlockingQueue(int queues) { BlockingQueueType queueType = BlockingQueueType.fromString(System.getProperty(RpcSystemConfig.SystemPropertyThreadPoolQueueNameAttr, "LinkedBlockingQueue")); switch (queueType) { case LINKED_BLOCKING_QUEUE: return new LinkedBlockingQueue(); case ARRAY_BLOCKING_QUEUE: return new ArrayBlockingQueue(RpcSystemConfig.PARALLEL * queues); case SYNCHRONOUS_QUEUE: return new SynchronousQueue(); } return null; } public static Executor getExecutor(int threads, int queues) { String name = "RpcThreadPool"; return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, createBlockingQueue(queues), new NamedThreadFactory(name, true), createPolicy()); } } 其中创建线程池方法getExecutor是依赖JDK自带的线程ThreadPoolExecutor的实现,参考JDK的帮助文档,可以发现其中的一种ThreadPoolExecutor构造方法重载实现的版本: 参数的具体含义如下: corePoolSize是线程池保留大小。 maximumPoolSize是线程池最大线程大小。 keepAliveTime是指空闲(idle)线程结束的超时时间。 unit用来指定keepAliveTime对应的时间单位,诸如:毫秒、秒、分钟、小时、天 等等。 workQueue用来存放待处理的任务队列。 handler用来具体指定,当任务队列填满、并且线程池最大线程大小也达到的情形下,线程池的一些应对措施策略。 NettyRPC的线程池支持的任务队列类型主要有以下三种: LinkedBlockingQueue:采用链表方式实现的无界任务队列,当然你可以额外指定其容量,使其有界。 ArrayBlockingQueue:有界的的数组任务队列。 SynchronousQueue:任务队列的容量固定为1,当客户端提交执行任务过来的时候,有进行阻塞。直到有个处理线程取走这个待执行的任务,否则会一直阻塞下去。 NettyRPC的线程池模型,当遇到线程池也无法处理的情形的时候,具体的应对措施策略主要有: AbortPolicy:直接拒绝执行,抛出rejectedExecution异常。 DiscardedPolicy:从任务队列的头部开始直接丢弃一半的队列元素,为任务队列“减负”。 CallerRunsPolicy:不抛弃任务,也不抛出异常,而是调用者自己来运行。这个是主要是因为过多的并行请求会加剧系统的负载,线程之间调度操作系统会频繁的进行上下文切换。当遇到线程池满的情况,与其频繁的切换、中断。不如把并行的请求,全部串行化处理,保证尽量少的处理延时,大概是我能想到的Doug Lea的设计初衷吧。 经过详细的介绍了线程池参数的具体内容之后,下面我就详细说一下,NettyRPC的线程池RpcThreadPool的工作流程: NettyRPC的线程池收到RPC数据处理请求之后,判断当前活动的线程数小于线程池设置的corePoolSize的大小的时候,会继续生成执行任务。 而当达到corePoolSize的大小的时候的时候,这个时候,线程池会把待执行的任务放入任务队列之中。 当任务队列也被存满了之后,如果当前活动的线程个数还是小于线程池中maximumPoolSize参数的设置,线程池还会继续分配出任务线程进行救急处理,并且会立马执行。 如果达到线程池中maximumPoolSize参数的设置的线程上限,线程池分派出来的救火队也无法处理的时候,线程池就会调用拒绝自保策略RejectedExecutionHandler进行处理。 NettyRPC中默认的线程池设置是把corePoolSize、maximumPoolSize都设置成16,任务队列设置成无界链表构成的阻塞队列。在应用中要根据实际的压力、吞吐量对NettyRPC的线程池参数进行合理的规划。目前NettyRPC暴露了一个JMX接口,JMX是“Java管理扩展的(Java Management Extensions)”的缩写,是一种类似J2EE的规范,这样就可以灵活的扩展系统的监控、管理功能。实时监控RPC服务器线程池任务的执行情况,具体JMX监控度量线程池关键指标代码实现如下: package com.newlandframework.rpc.parallel.jmx; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; /** * @author tangjie<https://github.com/tang-jie> * @filename:ThreadPoolStatus.java * @description:ThreadPoolStatus功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/13 */ @ManagedResource public class ThreadPoolStatus { private int poolSize; private int activeCount; private int corePoolSize; private int maximumPoolSize; private int largestPoolSize; private long taskCount; private long completedTaskCount; @ManagedOperation public int getPoolSize() { return poolSize; } @ManagedOperation public void setPoolSize(int poolSize) { this.poolSize = poolSize; } @ManagedOperation public int getActiveCount() { return activeCount; } @ManagedOperation public void setActiveCount(int activeCount) { this.activeCount = activeCount; } @ManagedOperation public int getCorePoolSize() { return corePoolSize; } @ManagedOperation public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; } @ManagedOperation public int getMaximumPoolSize() { return maximumPoolSize; } @ManagedOperation public void setMaximumPoolSize(int maximumPoolSize) { this.maximumPoolSize = maximumPoolSize; } @ManagedOperation public int getLargestPoolSize() { return largestPoolSize; } @ManagedOperation public void setLargestPoolSize(int largestPoolSize) { this.largestPoolSize = largestPoolSize; } @ManagedOperation public long getTaskCount() { return taskCount; } @ManagedOperation public void setTaskCount(long taskCount) { this.taskCount = taskCount; } @ManagedOperation public long getCompletedTaskCount() { return completedTaskCount; } @ManagedOperation public void setCompletedTaskCount(long completedTaskCount) { this.completedTaskCount = completedTaskCount; } } 线程池状态监控类:ThreadPoolStatus,具体监控的指标如下: poolSize:池中的当前线程数 activeCount:主动执行任务的近似线程数 corePoolSize:核心线程数 maximumPoolSize:允许的最大线程数 largestPoolSize:历史最大的线程数 taskCount:曾计划执行的近似任务总数 completedTaskCount:已完成执行的近似任务总数 其中corePoolSize、maximumPoolSize具体含义上文已经详细讲述,这里就不具体展开。 NettyRPC线程池监控JMX接口:ThreadPoolMonitorProvider,JMX通过JNDI-RMI的方式进行远程连接通讯,具体实现方式如下: package com.newlandframework.rpc.parallel.jmx; import com.newlandframework.rpc.netty.MessageRecvExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.EnableMBeanExport; import org.springframework.jmx.support.ConnectorServerFactoryBean; import org.springframework.jmx.support.MBeanServerConnectionFactoryBean; import org.springframework.jmx.support.MBeanServerFactoryBean; import org.springframework.remoting.rmi.RmiRegistryFactoryBean; import org.apache.commons.lang3.StringUtils; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.MBeanException; import javax.management.InstanceNotFoundException; import java.io.IOException; /** * @author tangjie<https://github.com/tang-jie> * @filename:ThreadPoolMonitorProvider.java * @description:ThreadPoolMonitorProvider功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/13 */ @Configuration @EnableMBeanExport @ComponentScan("com.newlandframework.rpc.parallel.jmx") public class ThreadPoolMonitorProvider { public final static String DELIMITER = ":"; public static String url; public static String jmxPoolSizeMethod = "setPoolSize"; public static String jmxActiveCountMethod = "setActiveCount"; public static String jmxCorePoolSizeMethod = "setCorePoolSize"; public static String jmxMaximumPoolSizeMethod = "setMaximumPoolSize"; public static String jmxLargestPoolSizeMethod = "setLargestPoolSize"; public static String jmxTaskCountMethod = "setTaskCount"; public static String jmxCompletedTaskCountMethod = "setCompletedTaskCount"; @Bean public ThreadPoolStatus threadPoolStatus() { return new ThreadPoolStatus(); } @Bean public MBeanServerFactoryBean mbeanServer() { return new MBeanServerFactoryBean(); } @Bean public RmiRegistryFactoryBean registry() { return new RmiRegistryFactoryBean(); } @Bean @DependsOn("registry") public ConnectorServerFactoryBean connectorServer() throws MalformedObjectNameException { MessageRecvExecutor ref = MessageRecvExecutor.getInstance(); String ipAddr = StringUtils.isNotEmpty(ref.getServerAddress()) ? StringUtils.substringBeforeLast(ref.getServerAddress(), DELIMITER) : "localhost"; url = "service:jmx:rmi://" + ipAddr + "/jndi/rmi://" + ipAddr + ":1099/nettyrpcstatus"; System.out.println("NettyRPC JMX MonitorURL : [" + url + "]"); ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean(); connectorServerFactoryBean.setObjectName("connector:name=rmi"); connectorServerFactoryBean.setServiceUrl(url); return connectorServerFactoryBean; } public static void monitor(ThreadPoolStatus status) throws IOException, MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException { MBeanServerConnectionFactoryBean mBeanServerConnectionFactoryBean = new MBeanServerConnectionFactoryBean(); mBeanServerConnectionFactoryBean.setServiceUrl(url); mBeanServerConnectionFactoryBean.afterPropertiesSet(); MBeanServerConnection connection = mBeanServerConnectionFactoryBean.getObject(); ObjectName objectName = new ObjectName("com.newlandframework.rpc.parallel.jmx:name=threadPoolStatus,type=ThreadPoolStatus"); connection.invoke(objectName, jmxPoolSizeMethod, new Object[]{status.getPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxActiveCountMethod, new Object[]{status.getActiveCount()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxCorePoolSizeMethod, new Object[]{status.getCorePoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxMaximumPoolSizeMethod, new Object[]{status.getMaximumPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxLargestPoolSizeMethod, new Object[]{status.getLargestPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxTaskCountMethod, new Object[]{status.getTaskCount()}, new String[]{long.class.getName()}); connection.invoke(objectName, jmxCompletedTaskCountMethod, new Object[]{status.getCompletedTaskCount()}, new String[]{long.class.getName()}); } } NettyRPC服务器启动成功之后,就可以通过JMX接口进行监控:可以打开jconsole,然后输入URL:service:jmx:rmi://127.0.0.1/jndi/rmi://127.0.0.1:1099/nettyrpcstatus,用户名、密码默认为空,点击连接按钮。 当有客户端进行RPC请求的时候,通过JMX可以看到如下的监控界面: 这个时候点击NettyRPC线程池各个监控指标的按钮,就可以直观的看到NettyRPC实际运行中,线程池的主要参数指标的实时监控。比如点击:getCompletedTaskCount,想查看一下目前已经完成的线程任务总数指标。具体情况如下图所示: 可以看到,目前已经处理了40280笔RPC请求。这样,我们就可以准实时监控NettyRPC线程池参数设置、容量规划是否合理,以便及时作出调整,合理的最大程度利用软硬件资源。 最后经过重构之后,NettyRPC服务端的Spring配置(NettyRPC/NettyRPC 2.0/main/resources/rpc-invoke-config-server.xml)如下: xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:nettyrpc="http://www.newlandframework.com/nettyrpc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.newlandframework.com/nettyrpc http://www.newlandframework.com/nettyrpc/nettyrpc.xsd"> <context:property-placeholder location="classpath:rpc-server.properties"/> <nettyrpc:service id="demoAddService" interfaceName="com.newlandframework.rpc.services.AddCalculate" ref="calcAddService">nettyrpc:service> <nettyrpc:service id="demoMultiService" interfaceName="com.newlandframework.rpc.services.MultiCalculate" ref="calcMultiService">nettyrpc:service> <nettyrpc:registry id="rpcRegistry" ipAddr="${rpc.server.addr}" protocol="PROTOSTUFFSERIALIZE">nettyrpc:registry> <bean id="calcAddService" class="com.newlandframework.rpc.services.impl.AddCalculateImpl">bean> <bean id="calcMultiService" class="com.newlandframework.rpc.services.impl.MultiCalculateImpl">bean> beans> 通过nettyrpc:service标签定义rpc服务器支持的服务接口,这里的样例声明了当前的rpc服务器提供了加法计算、乘法计算两种服务给客户端进行调用。具体通过Spring自定义标签的实现,大家可以自行参考github:NettyRPC/NettyRPC 2.0/main/java/com/newlandframework/rpc/spring(路径/包)中的实现代码,代码比较多得利用到了Spring框架的特性,希望大家能自行理解和分析。 然后通过bean标签声明了具体加法计算、乘法计算接口对应的实现类,都统一放在com.newlandframework.rpc.services包之中。 最后通过nettyrpc:registry注册了rpc服务器,ipAddr属性定义了该rpc服务器对应的ip/端口信息。protocol用来指定,当前rpc服务器支持的消息序列化协议类型。 目前已经实现的类型有:JDK原生的对象序列化(ObjectOutputStream/ObjectInputStream)、Kryo、Hessian、Protostuff一共四种序列化方式。 配置完成rpc-invoke-config-server.xml之后,就可以启动RPC服务器Main函数入口:com.newlandframework.rpc.boot.RpcServerStarter。通过Maven打包、部署在(Red Hat Enterprise Linux Server release 5.7 (Tikanga) 64位系统,其内核版本号:Kernel 2.6.18-274.el5 on an x86_64),可以启动NettyRPC,如果一切正常的话,在CRT终端上会显示如下输出: 这个时候再进行客户端的Spring配置(NettyRPC/NettyRPC 2.0/test/resources/rpc-invoke-config-client.xml)。 xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:nettyrpc="http://www.newlandframework.com/nettyrpc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.newlandframework.com/nettyrpc http://www.newlandframework.com/nettyrpc/nettyrpc.xsd"> <context:property-placeholder location="classpath:rpc-server.properties"/> <nettyrpc:reference id="addCalc" interfaceName="com.newlandframework.rpc.services.AddCalculate" protocol="PROTOSTUFFSERIALIZE" ipAddr="${rpc.server.addr}"/> <nettyrpc:reference id="multiCalc" interfaceName="com.newlandframework.rpc.services.MultiCalculate" protocol="PROTOSTUFFSERIALIZE" ipAddr="${rpc.server.addr}"/> beans> 其中加法计算、乘法计算的demo代码如下: package com.newlandframework.rpc.services; /** * @author tangjie<https://github.com/tang-jie> * @filename:Calculate.java * @description:Calculate功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public interface AddCalculate { //两数相加 int add(int a, int b); } package com.newlandframework.rpc.services.impl; import com.newlandframework.rpc.services.AddCalculate; /** * @author tangjie<https://github.com/tang-jie> * @filename:CalculateImpl.java * @description:CalculateImpl功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class AddCalculateImpl implements AddCalculate { //两数相加 public int add(int a, int b) { return a + b; } } package com.newlandframework.rpc.services; /** * @author tangjie<https://github.com/tang-jie> * @filename:Calculate.java * @description:Calculate功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public interface MultiCalculate { //两数相乘 int multi(int a, int b); } package com.newlandframework.rpc.services.impl; import com.newlandframework.rpc.services.MultiCalculate; /** * @author tangjie<https://github.com/tang-jie> * @filename:CalculateImpl.java * @description:CalculateImpl功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class MultiCalculateImpl implements MultiCalculate { //两数相乘 public int multi(int a, int b) { return a * b; } } 值得注意的是客户端NettyRPC的Spring配置除了指定调用远程RPC的服务服务信息之外,还必须配置远程RPC服务端对应的ip地址、端口信息、协议类型这些要素,而且必须和RPC服务端保持一致,这样才能正常的进行消息的编码、解码工作。 现在我们就模拟1W个瞬时并发的加法、乘法计算请求,一共2W笔请求操作,调用远程RPC服务器上的计算模块,我们默认采用protostuff序列化方式进行RPC消息的编码、解码。注意,测试代码的样例基于1W笔瞬时并发计算请求,不是1W笔循环进行计算请求,这个是衡量RPC服务器吞吐量的一个重要指标,因此这里的测试样例是基于CountDownLatch进行编写的,类java.util.concurrent.CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。这里是加法计算RPC请求、乘法计算RPC请求,在RPC客户端分别先启动1W个线程,这个时候先挂起,然后等待请求信号,瞬时发起RPC请求。具体代码如下: 首先是加法计算并发请求类AddCalcParallelRequestThread: package com.newlandframework.test; import com.newlandframework.rpc.services.AddCalculate; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; /** * @author tangjie<https://github.com/tang-jie> * @filename:AddCalcParallelRequestThread.java * @description:AddCalcParallelRequestThread功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class AddCalcParallelRequestThread implements Runnable { private CountDownLatch signal; private CountDownLatch finish; private int taskNumber = 0; private AddCalculate calc; public AddCalcParallelRequestThread(AddCalculate calc, CountDownLatch signal, CountDownLatch finish, int taskNumber) { this.signal = signal; this.finish = finish; this.taskNumber = taskNumber; this.calc = calc; } public void run() { try { //加法计算线程,先挂起,等待请求信号 signal.await(); //调用远程RPC服务器的加法计算方法模块 int add = calc.add(taskNumber, taskNumber); System.out.println("calc add result:[" + add + "]"); finish.countDown(); } catch (InterruptedException ex) { Logger.getLogger(AddCalcParallelRequestThread.class.getName()).log(Level.SEVERE, null, ex); } } } 其次是乘法计算并发请求类MultiCalcParallelRequestThread: package com.newlandframework.test; import com.newlandframework.rpc.services.MultiCalculate; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; /** * @author tangjie<https://github.com/tang-jie> * @filename:MultiCalcParallelRequestThread.java * @description:MultiCalcParallelRequestThread功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class MultiCalcParallelRequestThread implements Runnable { private CountDownLatch signal; private CountDownLatch finish; private int taskNumber = 0; private MultiCalculate calc; public MultiCalcParallelRequestThread(MultiCalculate calc, CountDownLatch signal, CountDownLatch finish, int taskNumber) { this.signal = signal; this.finish = finish; this.taskNumber = taskNumber; this.calc = calc; } public void run() { try { //乘法计算线程,先挂起,等待请求信号 signal.await(); //调用远程RPC服务器的乘法计算方法模块 int multi = calc.multi(taskNumber, taskNumber); System.out.println("calc multi result:[" + multi + "]"); finish.countDown(); } catch (InterruptedException ex) { Logger.getLogger(MultiCalcParallelRequestThread.class.getName()).log(Level.SEVERE, null, ex); } } } 现在写出一个调用的测试客户端RpcParallelTest,测试RPC服务器的性能,以及是否正确计算出最终的结果。测试客户端RpcParallelTest的具体代码如下: package com.newlandframework.test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import com.newlandframework.rpc.services.AddCalculate; import com.newlandframework.rpc.services.MultiCalculate; import org.apache.commons.lang3.time.StopWatch; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author tangjie<https://github.com/tang-jie> * @filename:RpcParallelTest.java * @description:RpcParallelTest功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class RpcParallelTest { public static void parallelAddCalcTask(AddCalculate calc, int parallel) throws InterruptedException { //开始计时 StopWatch sw = new StopWatch(); sw.start(); CountDownLatch signal = new CountDownLatch(1); CountDownLatch finish = new CountDownLatch(parallel); for (int index = 0; index < parallel; index++) { AddCalcParallelRequestThread client = new AddCalcParallelRequestThread(calc, signal, finish, index); new Thread(client).start(); } signal.countDown(); finish.await(); sw.stop(); String tip = String.format("加法计算RPC调用总共耗时: [%s] 毫秒", sw.getTime()); System.out.println(tip); } public static void parallelMultiCalcTask(MultiCalculate calc, int parallel) throws InterruptedException { //开始计时 StopWatch sw = new StopWatch(); sw.start(); CountDownLatch signal = new CountDownLatch(1); CountDownLatch finish = new CountDownLatch(parallel); for (int index = 0; index < parallel; index++) { MultiCalcParallelRequestThread client = new MultiCalcParallelRequestThread(calc, signal, finish, index); new Thread(client).start(); } signal.countDown(); finish.await(); sw.stop(); String tip = String.format("乘法计算RPC调用总共耗时: [%s] 毫秒", sw.getTime()); System.out.println(tip); } public static void addTask(AddCalculate calc, int parallel) throws InterruptedException { RpcParallelTest.parallelAddCalcTask(calc, parallel); TimeUnit.MILLISECONDS.sleep(30); } public static void multiTask(MultiCalculate calc, int parallel) throws InterruptedException { RpcParallelTest.parallelMultiCalcTask(calc, parallel); TimeUnit.MILLISECONDS.sleep(30); } public static void main(String[] args) throws Exception { //并行度10000 int parallel = 10000; //加载Spring配置信息 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:rpc-invoke-config-client.xml"); //并发进行RPC加法计算、乘法计算请求 addTask((AddCalculate) context.getBean("addCalc"), parallel); multiTask((MultiCalculate) context.getBean("multiCalc"), parallel); System.out.printf("[author tangjie] Netty RPC Server 消息协议序列化并发验证结束!\n\n"); context.destroy(); } } Netty RPC客户端运行情况,具体截图如下:下面是开始收到RPC服务器加法计算的结果截图。 好了,加法RPC请求计算完毕,控制台打印出请求耗时。 接着是调用RPC并行乘法计算请求,同样,控制台上也打印出请求耗时。 接着RPC的客户端运行完毕、退出,我们继续看下NettyRPC服务端的运行截图: 可以发现,NettyRPC的服务端确实都收到了来自客户端发起的RPC计算请求,给每个RPC消息标识出了唯一的消息编码,并进行了RPC计算处理之后,成功的把消息应答给了客户端。 经过一系列的模块重构,终于将NettyRPC重新升级了一下,经过这次重构工作,感觉自己对Netty、Spring、Java线程模型的了解更加深入了,不积跬步无以至千里,千里之行始于足下。学习靠的就是这样一点一滴的重复积累,才能将自己的能力提升一个台阶。 原创文章,加上本人才疏学浅,文笔有限,本文中有说得不对的地方,望各位同行不吝赐教。文中有忽略的地方希望读者可以补充,错误的地方还望斧正。 最后附上NettyRPC的开源项目地址:https://github.com/tang-jie/NettyRPC 中的NettyRPC 2.0项目。 感谢大家耐心阅读NettyRPC系列文章,如果本文对你有帮助,请点下推荐吧! 转载于:https://www.cnblogs.com/jietang/p/5983038.html 你可能感兴趣的:(基于Netty打造RPC服务器设计经验谈) 探索未来之声:趣玩语音识别新篇章——FunASR 乌芬维Maisie 探索未来之声:趣玩语音识别新篇章——FunASR去发现同类优质开源项目:https://gitcode.com/在这个数字时代,语音识别技术如同开启智能交互的金钥匙,而【FunASR】正是这把钥匙中的璀璨明珠。FunASR,一款由阿里巴巴达摩院倾力打造的基础语音识别工具包,不仅连接着学术探索的深邃与产业应用的实践,更是以“让语音识别更有趣”为使命,引领了一场声音转换为文字的技术革命。技术剖析:全面 DeepSeek模型微调的原理和方法 alankuo 人工智能 DeepSeek模型微调的原理迁移学习基础DeepSeek模型微调基于迁移学习的思想。预训练模型在大规模通用数据上进行了无监督或有监督的训练,学习到了丰富的语言知识、语义表示和通用模式。这些知识和模式具有一定的通用性,可以迁移到其他相关的任务中。在微调时,我们利用预训练模型已经学到的这些通用知识,针对特定的目标任务进行进一步的调整和优化,使得模型能够更好地适应新任务的需求。微调的参数更新机制在微调 Texas Instruments (TI) 系列:TIVA C 系列 (基于 ARM Cortex-M4)_(7).TIVA C系列UART通信 kkchenkx 单片机开发c语言arm开发开发语言嵌入式硬件单片机 TIVAC系列UART通信1.UART通信原理UART(UniversalAsynchronousReceiver-Transmitter)是一种常见的串行通信接口,用于在两个设备之间传输数据。TIVAC系列单片机基于ARMCortex-M4内核,提供了多个UART模块,支持全双工通信。UART通信的基本原理如下:1.1异步通信UART通信是一种异步通信方式,这意味着发送方和接收方之间没有共享的时 文档检索服务平台 liupan6889 产品设计全文检索elasticsearch全文检索开源软件 文档检索服务平台是基于Elasticsearch的全文检索,包含数据采集、数据清洗、数据转换、数据检索等模块。项目地址:Github、国内Gitee演示地址:http://silianpan.cn/gdss/以下是演示角色和账号(密码同账号):测试用户:test超级管理员:admin系统架构部分截图登录全文检索文章详情 请简述一下Prefab(预制体)的本质是什么? Nicole Potter U3D客户端面试题汇总游戏面试 在Unity中,Prefab(预制体)是一种非常重要的资产类型。Prefab本质上是一个可重复使用(开发者可以在场景中多次实例化同一个预制体)的游戏对象模板(预制体就像一个模板,对预制体本身的修改会自动应用到所有基于该预制体创建的实例上。例如,如果修改了角色预制体的动画参数,那么场景中所有该角色预制体的实例都会更新为新的动画设置。),它存储了一个或多个游戏对象及其组件的配置信息(例如,一个角色预制 使用ezuikit-js封装一个对接摄像头的组件 Abao javascriptvue.jselementui ezuikit-js是一个基于JavaScript的视频播放库,主要用于在网页中嵌入实时视频流播放功能。它通常用于与支持RTSP、RTMP、HLS等协议的摄像头或视频流服务器进行交互,提供流畅的视频播放体验。主要功能多协议支持:支持RTSP、RTMP、HLS等主流视频流协议。实时播放:低延迟播放实时视频流。多平台兼容:支持PC端和移动端浏览器。丰富的API:提供JavaScriptAPI,方便开发 MySQL 缓存详解,让你的应用跑得更快! 小电玩 MySQLmysqljava MySQL缓存介绍一,介绍MySQL缓存是指MySQL数据库服务器中的内存区域,用于存储经常访问的数据和查询结果,以提高查询性能和响应时间。MySQL缓存主要包括查询缓存和InnoDB缓冲池。查询缓存:MySQL的查询缓存是一种基于结果的缓存机制,它可以缓存查询语句和对应的结果集。当一个查询被执行时,MySQL会首先检查查询缓存,如果缓存中已经存在相同的查询语句和参数,则直接返回缓存中的结果,而不 14.5 Auto-GPT:基于Agent的AGI实验如何重新定义人工智能未来? 少林码僧 AI大模型应用实战专栏gptagi人工智能transformer深度学习langchain Auto-GPT:基于Agent的AGI实验如何重新定义人工智能未来?关键词:自主智能体范式、AGI演进路径、动态环境交互、认知架构革命、社会级智能网络一、AGI演进的关键瓶颈与Agent范式的突破1.1传统AI系统的能力天花板 【可靠有效】springboot使用netty搭建TCP服务器 weixin_43833540 springbootnettytcp NettyNetty是一个高性能、异步事件驱动的网络应用程序框架,它提供了对并发和异步编程的抽象,使得开发网络应用程序变得更加简单和高效。在Netty中,EventLoopGroup是处理I/O操作的多线程事件循环器。在上面的示例中,我们创建了两个EventLoopGroup实例:bossGroup和workerGroup。bossGroup负责接收客户端的连接请求,并将这些连接分配给worker 网络通信(待补充) 四无青年203 golang 网络通信互联网中主机和主机连接必须遵守特定的要求,这个要求成为协议osi开放式系统互联,定义了计算机互联时网络通信的7层目前大规模使用的是tcp/ip协议:应用层:合并osi中567层(绘画,表示,应用)常用协议:http,ftp,smtp,pop3,ssl,rpc传输层:osi中第四层常用协议:tcp,udp网络层:osi中第三层常用协议:ip,ipv4,ipv6网络接口层:osi中第1,2层i FastAPI 基本路由 lly202406 开发语言 FastAPI基本路由引言FastAPI是一个现代、快速(高性能)的Web框架,用于构建API,用Python3.6+类型提示。FastAPI基于标准Python类型提示,并使用Starlette和Pydantic,提供了自动验证、数据转换、自动文档和交互式API测试。在本文中,我们将探讨FastAPI的基本路由,包括路由的定义、参数处理、响应格式等关键概念。FastAPI路由基础路由定义在Fas SAP UI5 应用的 OData XML 格式的元数据请求解析原理,基于 DOMParser 汪子熙 SAPUI5百科全书xml 前一篇文章SAPUI5应用的OData元数据请求响应的解析原理分析我们介绍了SAPUI5OData元数据解析的入口。本文继续介绍基于DOMParser的XML数据解析原理。入口如下:varxmlParse=function(text){///ReturnsanXMLDOMdocumentfromthespecifiedtext.///<paramname="tex ansible面试题 三颗草丶 1024程序员节 简述Ansible及其优势?Ansible是一款极其简单的开源的自动化运维工具,基于Python开发,集合了众多运维工具(puppet,cfengine,chef,func,fabric)的优点。实现了批量系统配置,批量程序部署,批量运行命令等功能。同时Ansible是基于模块工作,其实现批量部署的是ansible所运行的模块。Ansible其他重要的优势:跨平台支持:Ansible在物理、虚拟、 springboot 基于@Scheduled注解 实现定时任务 beidaol springboot定时器定时器Scheduled 前言定时任务通常有三种完成方法java自带的APIjava.util.Timer类java.util.TimerTask类在JDK中,内置了两个类,可以实现定时任务的功能:java.util.Timer:可以通过创建java.util.TimerTask调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个Timer里的多个TimerTask任务,如果一个TimerTask任务在执行中, Spring Autowired扩展策略模式 WwJoyous java策略模式springjava 背景策略模式在实际开发中使用非常频繁的设计模式;通常都是写完策略类后,还要专门写一个类进行策略分发,比较繁琐;spring中利用@Autowired注解,可以支持自动注入list、map类型@AutowiredprivateMapmap;map的key,是实例bean的beanName;往往在实际使用过程中,策略key是业务场景类型还不是beanName;实现基于注解的方式完成策略模式通过spri 基于 openEuler 构建 LVS-DR 群集 致奋斗的我们 openEulerLinux云原生高级lvsmysql服务器linuxopenEulernginx负载均衡 目录对比LVS负载均衡群集的NAT模式和DR模式,比较其各自的优势NAT模式(网络地址转换模式)DR模式(直接路由模式)基于openEuler构建LVS-DR群集实验准备环境配置web服务器web1web2首先下载nginx设置连接界面测试在两台web服务器中增加VIP的相关配置绑定地址ARP抑制然后使其生效配置负载均衡在192.168.1.11上接着下载管理工具ipvsadmLVS配置查看配置测 Spring策略模式示例 小徐博客 spring策略模式java 在本例中,我们将学习中的策略模式春天。我们将介绍注入策略的不同方法,从简单的基于列表的方法到更有效的基于地图的方法。为了说明这个概念,我们将使用《哈利·波特》系列中的三个不可饶恕咒——阿瓦达·凯达维拉、克鲁西奥和因佩里奥。战略模式是什么?战略模式是一种设计原则,允许您在运行时在不同的算法或行为之间切换。它允许您在不改变应用程序核心逻辑的情况下插入不同的策略,从而使您的代码具有灵活性和适应性。这种方 覆盖从供应、生产、销售到运营的全过程,引领行业数智化转型新方向的智慧快消开源了 AI服务老曹 开源人工智能自动化音视频能源 智慧快消视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。基于多年的深度学习技术研究和业务应用为基础,集深度学习核心训练和推理框架、基础模型库、端到端开发套件、丰富的工具组件于一体,是中国首个自主研发、功能完备、开源开放的产业级深度学习平台。基 前端打包工具rollup、webpack、vite的区别 手掌日月摘星辰 前端面试秘籍webpack前端viterollup 总结rollup更适合打包库,webpack更适合打包项目,vite基于rollup实现了热更新也适合打包项目。1、Rollup优点:1.Rollup是一款ESModules打包器,从作用上来看,Rollup与Webpack非常类似。不过相比于Webpack,Rollup要小巧的多,打包生成的文件更小。(识别commonJs需要插件)2.热更新:Rollup不支持HMR,在对js以外的模块的支持上 基于nodejs+vuetify3+ts+nuxt框架集成模块组件解析-seo优化第二章节+ 垣宇 开发框架/组件vue.jsnode.jstypescriptvscodexhtml中间件性能优化 一.身份验证请参考身份验证二.SEO优化+谷歌等开源文档示例SEO优化第一章节三.SEO优化第二章节1.Schema.org介绍nuxt提供的Schema.org可能不会提供直接的排名提升,但它允许您在搜索结果中呈现丰富的片段。丰富的片段已经被证明可以提高点击率,并在用户点击你的网站之前为他们提供更多的信息。Schema.org提供了一个简单的API来为您的next应用程序构建Schema.org 大数据可视化设计实用技巧全攻略 UI设计兰亭妙微 信息可视化数据分析数据挖掘 在大数据时代,数据可视化设计已成为将复杂数据转化为直观洞察的关键。下面就为大家分享一些实用技巧,助你打造出出色的数据可视化作品。一、选择合适的图表类型不同类型的图表适用于不同的数据展示需求。柱状图擅长比较数据大小,折线图则能清晰呈现数据随时间的变化趋势,而饼图用于展示各部分占比。例如,在展示不同产品的销量对比时,柱状图一目了然;分析股票价格的长期走势,折线图更为合适;呈现市场份额分布,饼图效果最佳 金融大模型应用的机遇与挑战 Python程序员罗宾 金融人工智能语言模型数据库自然语言处理 大模型本质特征大模型通常指大语言模型(LargeLanguageModel,LLM),是基于深度学习算法的自然语言处理技术,是通用大模型。大模型也在从单一自然语言处理模态向语音、图像等多模态大模型演进。目前国内外推出了众多的大模型,国内就不下上百款,也因此被称为“百模大战”或“千模大战”。但很多所谓的“大模型”仅是叫“大模型”而已,不管参数量多少,都不能称为真正的大模型。参数量是大模型的一个特征, 基于SpringBoot的大学生综合能力测评管理系统 计算机学姐 Java精选实战项目源码SpringBoot源码Vue源码springboot后端javaspringvue.jsmysqljava-ee 作者:计算机学姐开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码精品专栏:Java精选实战项目源码、Python精选实战项目源码、大数据精选实战项目源码系统展示【2025最新】基于Java+SpringBoot+Vu 杰和科技GAM-AI视觉识别管理系统,让AI走进零售营销 G***技 人工智能大数据系统架构 在数字化浪潮席卷全球零售业的今天,如何精准触达顾客需求、优化运营效率、提升门店业绩,成为实体商业破局的关键。GAM-AI视觉识别管理系统杰和科技智能零售管理系统:GAM-AI视觉识别管理系统,以AI视觉识别+大数据分析+边缘计算为核心技术,打造集“精准营销、客流洞察、智能决策”于一体的全场景解决方案,助力零售门店实现从“人货场”到“智货场”的智慧升级。系统部署以杰和科技安卓媒体播放器作为核心硬件, 1秒响应、90%决策准确率!京东商家智能助手的技术探索 京东零售技术 人工智能大模型 引言多智能体的架构演进过程:第一阶段:B商城工单自动回复,LLM和RAG结合知识库应答,无法解决工具调用。第二阶段:京东招商站,单一Agent处理知识库问答和工具调用,准确率低&LLM模型幻觉,场景区分度差。第三阶段:京麦智能助手,引入multi-agent架构,master+subagents协同工作模式,把问题分而治之,显著提升准确率。商家助手的算法底座是基于大语言模型(LLM)构建的Mul 【Docker教程】Docker安装教程 神马都会亿点点的毛毛张 #软件安装与配置后端dockereureka容器后端运维java 大家好!我是毛毛张!个人首页:神马都会亿点点的毛毛张今天毛毛张分享的是基于CentOS7系统下安装Docker的教程文章目录1.卸载旧版2.配置Docker的yum库3.安装Docker4.启动和校验5.配置镜像加速6.官网教程1.卸载旧版首先如果系统中已经存在旧的Docker,则先通过下面命令进行卸载:yumremovedocker\docker-client\docker-client-lat Springboot(四十九)SpringBoot3整合jetcache缓存 camellias_ springboot缓存后端 上文中我们学习了springboot中缓存的基本使用。缓存分为本地caffeine缓存和远程redis缓存。现在有一个小小的问题,我想使用本地caffeine缓存和远程redis缓存组成二级缓存。还想保证他们的一致性,这个事情该怎么办呢?Jetcache框架为我们解决了这个问题。JetCache是一个由阿里巴巴开发的基于Java的缓存系统封装,旨在通过统一的API和注解简化缓存的使用。JetC a股股票高频行情数据逐笔分析历史数据下载20250221 hightick 数据分析数据挖掘数据库金融 a股股票高频行情数据逐笔分析历史数据下载20250221基于Level2的逐笔成交和逐笔委托数据,这种毫秒级别的记录能分析出许多关键信息,如庄家意图、虚假动作,使所有交易行为暴露在阳光下。这对交易大师分析主力习性非常有帮助,对人工智能的学习也极具意义,数据量大且精准。以下是今日Level2逐笔成交与委托数据分析的部分股票现象:level2逐笔成交逐笔委托数据下载链接:https://pan.bai 网络安全(黑客)——自学手册2024 小言同学喜欢挖漏洞 web安全网络安全网络安全深度学习信息安全渗透测试 一、什么是网络安全网络安全可以基于攻击和防御视角来分类,我们经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面性,例如Web安全技术,既有Web渗透,也有Web防御技术(WAF)。作为一个合格的网络安全工程师,应该做到攻守兼备,毕竟知己知彼,才能百战百胜。二、怎样规划网络安全如果你是一 网络安全(黑客)——自学2025 网安大师兄 web安全安全网络网络安全linux 基于入门网络安全/黑客打造的:黑客&网络安全入门&进阶学习资源包前言什么是网络安全网络安全可以基于攻击和防御视角来分类,我们经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。如何成为一名黑客很多朋友在学习安全方面都会半路转行,因为不知如何去学,在这里,我将这个整份答案分为黑客(网络安全)入门必备、黑客(网络安全)职业指南、黑客(网络安全)学习 二分查找排序算法 周凡杨 java二分查找排序算法折半 一:概念 二分查找又称 折半查找( 折半搜索/ 二分搜索),优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而 查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表 分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步 java中的BigDecimal bijian1013 javaBigDecimal 在项目开发过程中出现精度丢失问题,查资料用BigDecimal解决,并发现如下这篇BigDecimal的解决问题的思路和方法很值得学习,特转载。 原文地址:http://blog.csdn.net/ugg/article/de Shell echo命令详解 daizj echoshell Shell echo命令 Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出。命令格式: echo string 您可以使用echo实现更复杂的输出格式控制。 1.显示普通字符串: echo "It is a test" 这里的双引号完全可以省略,以下命令与上面实例效果一致: echo Itis a test 2.显示转义 Oracle DBA 简单操作 周凡杨 oracle dba sql --执行次数多的SQL select sql_text,executions from ( select sql_text,executions from v$sqlarea order by executions desc ) where rownum<81; &nb 画图重绘 朱辉辉33 游戏 我第一次接触重绘是编写五子棋小游戏的时候,因为游戏里的棋盘是用线绘制的,而这些东西并不在系统自带的重绘里,所以在移动窗体时,棋盘并不会重绘出来。所以我们要重写系统的重绘方法。 在重写系统重绘方法时,我们要注意一定要调用父类的重绘方法,即加上super.paint(g),因为如果不调用父类的重绘方式,重写后会把父类的重绘覆盖掉,而父类的重绘方法是绘制画布,这样就导致我们 线程之初体验 西蜀石兰 线程 一直觉得多线程是学Java的一个分水岭,懂多线程才算入门。 之前看《编程思想》的多线程章节,看的云里雾里,知道线程类有哪几个方法,却依旧不知道线程到底是什么?书上都写线程是进程的模块,共享线程的资源,可是这跟多线程编程有毛线的关系,呜呜。。。 线程其实也是用户自定义的任务,不要过多的强调线程的属性,而忽略了线程最基本的属性。 你可以在线程类的run()方法中定义自己的任务,就跟正常的Ja linux集群互相免登陆配置 林鹤霄 linux 配置ssh免登陆 1、生成秘钥和公钥 ssh-keygen -t rsa 2、提示让你输入,什么都不输,三次回车之后会在~下面的.ssh文件夹中多出两个文件id_rsa 和 id_rsa.pub 其中id_rsa为秘钥,id_rsa.pub为公钥,使用公钥加密的数据只有私钥才能对这些数据解密 c mysql : Lock wait timeout exceeded; try restarting transaction aigo mysql 原文:http://www.cnblogs.com/freeliver54/archive/2010/09/30/1839042.html 原因是你使用的InnoDB 表类型的时候, 默认参数:innodb_lock_wait_timeout设置锁等待的时间是50s, 因为有的锁等待超过了这个时间,所以抱错. 你可以把这个时间加长,或者优化存储 Socket编程 基本的聊天实现。 alleni123 socket public class Server { //用来存储所有连接上来的客户 private List<ServerThread> clients; public static void main(String[] args) { Server s = new Server(); s.startServer(9988); } publi 多线程监听器事件模式(一个简单的例子) 百合不是茶 线程监听模式 多线程的事件监听器模式 监听器时间模式经常与多线程使用,在多线程中如何知道我的线程正在执行那什么内容,可以通过时间监听器模式得到 创建多线程的事件监听器模式 思路: 1, 创建线程并启动,在创建线程的位置设置一个标记 2,创建队 spring InitializingBean接口 bijian1013 javaspring spring的事务的TransactionTemplate,其源码如下: public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean{ ... } TransactionTemplate继承了DefaultT Oracle中询表的权限被授予给了哪些用户 bijian1013 oracle数据库权限 Oracle查询表将权限赋给了哪些用户的SQL,以备查用。 select t.table_name as "表名", t.grantee as "被授权的属组", t.owner as "对象所在的属组" 【Struts2五】Struts2 参数传值 bit1129 struts2 Struts2中参数传值的3种情况 1.请求参数绑定到Action的实例字段上 2.Action将值传递到转发的视图上 3.Action将值传递到重定向的视图上 一、请求参数绑定到Action的实例字段上以及Action将值传递到转发的视图上 Struts可以自动将请求URL中的请求参数或者表单提交的参数绑定到Action定义的实例字段上,绑定的规则使用ognl表达式语言 【Kafka十四】关于auto.offset.reset[Q/A] bit1129 kafka I got serveral questions about auto.offset.reset. This configuration parameter governs how consumer read the message from Kafka when there is no initial offset in ZooKeeper or nginx gzip压缩配置 ronin47 nginx gzip 压缩范例 nginx gzip压缩配置 更多 0 nginx gzip 配置 随着nginx的发展,越来越多的网站使用nginx,因此nginx的优化变得越来越重要,今天我们来看看nginx的gzip压缩到底是怎么压缩的呢? gzip(GNU-ZIP)是一种压缩技术。经过gzip压缩后页面大小可以变为原来的30%甚至更小,这样,用 java-13.输入一个单向链表,输出该链表中倒数第 k 个节点 bylijinnan java two cursors. Make the first cursor go K steps first. /* * 第 13 题:题目:输入一个单向链表,输出该链表中倒数第 k 个节点 */ public void displayKthItemsBackWard(ListNode head,int k){ ListNode p1=head,p2=head; Spring源码学习-JdbcTemplate queryForObject bylijinnan javaspring JdbcTemplate中有两个可能会混淆的queryForObject方法: 1. Object queryForObject(String sql, Object[] args, Class requiredType) 2. Object queryForObject(String sql, Object[] args, RowMapper rowMapper) 第1个方法是只查 [冰川时代]在冰川时代,我们需要什么样的技术? comsci 技术 看美国那边的气候情况....我有个感觉...是不是要进入小冰期了? 那么在小冰期里面...我们的户外活动肯定会出现很多问题...在室内呆着的情况会非常多...怎么在室内呆着而不发闷...怎么用最低的电力保证室内的温度.....这都需要技术手段... &nb js 获取浏览器型号 cuityang js浏览器 根据浏览器获取iphone和apk的下载地址 <!DOCTYPE html> <html> <head> <meta charset="utf-8" content="text/html"/> <meta name= C# socks5详解 转 dalan_123 socketC# http://www.cnblogs.com/zhujiechang/archive/2008/10/21/1316308.html 这里主要讲的是用.NET实现基于Socket5下面的代理协议进行客户端的通讯,Socket4的实现是类似的,注意的事,这里不是讲用C#实现一个代理服务器,因为实现一个代理服务器需要实现很多协议,头大,而且现在市面上有很多现成的代理服务器用,性能又好, 运维 Centos问题汇总 dcj3sjt126com 云主机 一、sh 脚本不执行的原因 sh脚本不执行的原因 只有2个 1.权限不够 2.sh脚本里路径没写完整。 二、解决You have new mail in /var/spool/mail/root 修改/usr/share/logwatch/default.conf/logwatch.conf配置文件 MailTo = MailFrom 三、查询连接数 Yii防注入攻击笔记 dcj3sjt126com sqlWEB安全yii 网站表单有注入漏洞须对所有用户输入的内容进行个过滤和检查,可以使用正则表达式或者直接输入字符判断,大部分是只允许输入字母和数字的,其它字符度不允许;对于内容复杂表单的内容,应该对html和script的符号进行转义替换:尤其是<,>,',"",&这几个符号 这里有个转义对照表: http://blog.csdn.net/xinzhu1990/articl MongoDB简介[一] eksliang mongodbMongoDB简介 MongoDB简介 转载请出自出处:http://eksliang.iteye.com/blog/2173288 1.1易于使用 MongoDB是一个面向文档的数据库,而不是关系型数据库。与关系型数据库相比,面向文档的数据库不再有行的概念,取而代之的是更为灵活的“文档”模型。 另外,不 zookeeper windows 入门安装和测试 greemranqq zookeeper安装分布式 一、序言 以下是我对zookeeper 的一些理解: zookeeper 作为一个服务注册信息存储的管理工具,好吧,这样说得很抽象,我们举个“栗子”。 栗子1号: 假设我是一家KTV的老板,我同时拥有5家KTV,我肯定得时刻监视 Spring之使用事务缘由(2-注解实现) ihuning spring Spring事务注解实现 1. 依赖包: 1.1 spring包: spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0. iOS App Launch Option 啸笑天 option iOS 程序启动时总会调用application:didFinishLaunchingWithOptions:,其中第二个参数launchOptions为NSDictionary类型的对象,里面存储有此程序启动的原因。 launchOptions中的可能键值见UIApplication Class Reference的Launch Options Keys节 。 1、若用户直接 jdk与jre的区别(_) macroli javajvmjdk 简单的说JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境。SDK是Software Development Kit 一般指软件开发包,可以包括函数库、编译程序等。 JDK就是Java Development Kit JRE是Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,而不是开发者。 如果安装了JDK,会发同你 Updates were rejected because the tip of your current branch is behind qiaolevip 学习永无止境每天进步一点点众观千象git $ git push joe prod-2295-1 To git@git.dianrong.com:joe.le/dr-frontend.git ! [rejected] prod-2295-1 -> prod-2295-1 (non-fast-forward) error: failed to push some refs to 'git@git.dianron [一起学Hive]之十四-Hive的元数据表结构详解 superlxw1234 hivehive元数据结构 关键字:Hive元数据、Hive元数据表结构 之前在 “[一起学Hive]之一–Hive概述,Hive是什么”中介绍过,Hive自己维护了一套元数据,用户通过HQL查询时候,Hive首先需要结合元数据,将HQL翻译成MapReduce去执行。 本文介绍一下Hive元数据中重要的一些表结构及用途,以Hive0.13为例。 文章最后面,会以一个示例来全面了解一下, Spring 3.2.14,4.1.7,4.2.RC2发布 wiselyman Spring 3 Spring 3.2.14、4.1.7及4.2.RC2于6月30日发布。 其中Spring 3.2.1是一个维护版本(维护周期到2016-12-31截止),后续会继续根据需求和bug发布维护版本。此时,Spring官方强烈建议升级Spring框架至4.1.7 或者将要发布的4.2 。 其中Spring 4.1.7主要包含这些更新内容。 按字母分类: ABCDEFGHIJKLMNOPQRSTUVWXYZ其他
同样为了提高Protostuff序列化/反序列化类的利用效率,我们可以对其进行池化处理,而不要频繁的创建、销毁对象。现在给出Protostuff池化处理类:ProtostuffSerializeFactory、ProtostuffSerializePool的实现代码:
package com.newlandframework.rpc.serialize.protostuff; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffSerializeFactory.java * @description:ProtostuffSerializeFactory功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffSerializeFactory extends BasePooledObjectFactory { public ProtostuffSerialize create() throws Exception { return createProtostuff(); } public PooledObject wrap(ProtostuffSerialize hessian) { return new DefaultPooledObject(hessian); } private ProtostuffSerialize createProtostuff() { return new ProtostuffSerialize(); } }
package com.newlandframework.rpc.serialize.protostuff; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffSerializePool.java * @description:ProtostuffSerializePool功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffSerializePool { private GenericObjectPool ProtostuffPool; volatile private static ProtostuffSerializePool poolFactory = null; private ProtostuffSerializePool() { ProtostuffPool = new GenericObjectPool(new ProtostuffSerializeFactory()); } public static ProtostuffSerializePool getProtostuffPoolInstance() { if (poolFactory == null) { synchronized (ProtostuffSerializePool.class) { if (poolFactory == null) { poolFactory = new ProtostuffSerializePool(); } } } return poolFactory; } public ProtostuffSerializePool(final int maxTotal, final int minIdle, final long maxWaitMillis, final long minEvictableIdleTimeMillis) { ProtostuffPool = new GenericObjectPool(new ProtostuffSerializeFactory()); GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMinIdle(minIdle); config.setMaxWaitMillis(maxWaitMillis); config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); ProtostuffPool.setConfig(config); } public ProtostuffSerialize borrow() { try { return getProtostuffPool().borrowObject(); } catch (final Exception ex) { ex.printStackTrace(); return null; } } public void restore(final ProtostuffSerialize object) { getProtostuffPool().returnObject(object); } public GenericObjectPool getProtostuffPool() { return ProtostuffPool; } }
现在有了Protostuff池化处理类,我们就通过它来实现NettyRPC的编码、解码接口,达到对RPC消息编码、解码的目的。首先是Protostuff方式实现的RPC解码器代码:
package com.newlandframework.rpc.serialize.protostuff; import com.newlandframework.rpc.serialize.MessageCodecUtil; import com.newlandframework.rpc.serialize.MessageDecoder; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffDecoder.java * @description:ProtostuffDecoder功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffDecoder extends MessageDecoder { public ProtostuffDecoder(MessageCodecUtil util) { super(util); } }
然后是Protostuff方式实现的RPC编码器代码:
package com.newlandframework.rpc.serialize.protostuff; import com.newlandframework.rpc.serialize.MessageCodecUtil; import com.newlandframework.rpc.serialize.MessageEncoder; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffEncoder.java * @description:ProtostuffEncoder功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffEncoder extends MessageEncoder { public ProtostuffEncoder(MessageCodecUtil util) { super(util); } }
最后重构出Protostuff方式的RPC编码、解码器工具类ProtostuffCodecUtil的实现代码:
package com.newlandframework.rpc.serialize.protostuff; import com.google.common.io.Closer; import com.newlandframework.rpc.serialize.MessageCodecUtil; import io.netty.buffer.ByteBuf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * @author tangjie<https://github.com/tang-jie> * @filename:ProtostuffCodecUtil.java * @description:ProtostuffCodecUtil功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class ProtostuffCodecUtil implements MessageCodecUtil { private static Closer closer = Closer.create(); private ProtostuffSerializePool pool = ProtostuffSerializePool.getProtostuffPoolInstance(); private boolean rpcDirect = false; public boolean isRpcDirect() { return rpcDirect; } public void setRpcDirect(boolean rpcDirect) { this.rpcDirect = rpcDirect; } public void encode(final ByteBuf out, final Object message) throws IOException { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); closer.register(byteArrayOutputStream); ProtostuffSerialize protostuffSerialization = pool.borrow(); protostuffSerialization.serialize(byteArrayOutputStream, message); byte[] body = byteArrayOutputStream.toByteArray(); int dataLength = body.length; out.writeInt(dataLength); out.writeBytes(body); pool.restore(protostuffSerialization); } finally { closer.close(); } } public Object decode(byte[] body) throws IOException { try { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); closer.register(byteArrayInputStream); ProtostuffSerialize protostuffSerialization = pool.borrow(); protostuffSerialization.setRpcDirect(rpcDirect); Object obj = protostuffSerialization.deserialize(byteArrayInputStream); pool.restore(protostuffSerialization); return obj; } finally { closer.close(); } } }
这样就使得NettyRPC的消息序列化又多了一种方式,进一步增强了其RPC消息网络传输的能力。
其次是优化了NettyRPC服务端的线程模型,使得RPC消息处理线程池对任务的队列容器的支持更加多样。具体RPC异步处理线程池RpcThreadPool的代码如下:
package com.newlandframework.rpc.parallel; import com.newlandframework.rpc.core.RpcSystemConfig; import com.newlandframework.rpc.parallel.policy.AbortPolicy; import com.newlandframework.rpc.parallel.policy.BlockingPolicy; import com.newlandframework.rpc.parallel.policy.CallerRunsPolicy; import com.newlandframework.rpc.parallel.policy.DiscardedPolicy; import com.newlandframework.rpc.parallel.policy.RejectedPolicy; import com.newlandframework.rpc.parallel.policy.RejectedPolicyType; import java.util.concurrent.Executor; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.RejectedExecutionHandler; /** * @author tangjie<https://github.com/tang-jie> * @filename:RpcThreadPool.java * @description:RpcThreadPool功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class RpcThreadPool { private static RejectedExecutionHandler createPolicy() { RejectedPolicyType rejectedPolicyType = RejectedPolicyType.fromString(System.getProperty(RpcSystemConfig.SystemPropertyThreadPoolRejectedPolicyAttr, "AbortPolicy")); switch (rejectedPolicyType) { case BLOCKING_POLICY: return new BlockingPolicy(); case CALLER_RUNS_POLICY: return new CallerRunsPolicy(); case ABORT_POLICY: return new AbortPolicy(); case REJECTED_POLICY: return new RejectedPolicy(); case DISCARDED_POLICY: return new DiscardedPolicy(); } return null; } private static BlockingQueue createBlockingQueue(int queues) { BlockingQueueType queueType = BlockingQueueType.fromString(System.getProperty(RpcSystemConfig.SystemPropertyThreadPoolQueueNameAttr, "LinkedBlockingQueue")); switch (queueType) { case LINKED_BLOCKING_QUEUE: return new LinkedBlockingQueue(); case ARRAY_BLOCKING_QUEUE: return new ArrayBlockingQueue(RpcSystemConfig.PARALLEL * queues); case SYNCHRONOUS_QUEUE: return new SynchronousQueue(); } return null; } public static Executor getExecutor(int threads, int queues) { String name = "RpcThreadPool"; return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, createBlockingQueue(queues), new NamedThreadFactory(name, true), createPolicy()); } }
其中创建线程池方法getExecutor是依赖JDK自带的线程ThreadPoolExecutor的实现,参考JDK的帮助文档,可以发现其中的一种ThreadPoolExecutor构造方法重载实现的版本:
参数的具体含义如下:
NettyRPC的线程池支持的任务队列类型主要有以下三种:
NettyRPC的线程池模型,当遇到线程池也无法处理的情形的时候,具体的应对措施策略主要有:
经过详细的介绍了线程池参数的具体内容之后,下面我就详细说一下,NettyRPC的线程池RpcThreadPool的工作流程:
NettyRPC中默认的线程池设置是把corePoolSize、maximumPoolSize都设置成16,任务队列设置成无界链表构成的阻塞队列。在应用中要根据实际的压力、吞吐量对NettyRPC的线程池参数进行合理的规划。目前NettyRPC暴露了一个JMX接口,JMX是“Java管理扩展的(Java Management Extensions)”的缩写,是一种类似J2EE的规范,这样就可以灵活的扩展系统的监控、管理功能。实时监控RPC服务器线程池任务的执行情况,具体JMX监控度量线程池关键指标代码实现如下:
package com.newlandframework.rpc.parallel.jmx; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; /** * @author tangjie<https://github.com/tang-jie> * @filename:ThreadPoolStatus.java * @description:ThreadPoolStatus功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/13 */ @ManagedResource public class ThreadPoolStatus { private int poolSize; private int activeCount; private int corePoolSize; private int maximumPoolSize; private int largestPoolSize; private long taskCount; private long completedTaskCount; @ManagedOperation public int getPoolSize() { return poolSize; } @ManagedOperation public void setPoolSize(int poolSize) { this.poolSize = poolSize; } @ManagedOperation public int getActiveCount() { return activeCount; } @ManagedOperation public void setActiveCount(int activeCount) { this.activeCount = activeCount; } @ManagedOperation public int getCorePoolSize() { return corePoolSize; } @ManagedOperation public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; } @ManagedOperation public int getMaximumPoolSize() { return maximumPoolSize; } @ManagedOperation public void setMaximumPoolSize(int maximumPoolSize) { this.maximumPoolSize = maximumPoolSize; } @ManagedOperation public int getLargestPoolSize() { return largestPoolSize; } @ManagedOperation public void setLargestPoolSize(int largestPoolSize) { this.largestPoolSize = largestPoolSize; } @ManagedOperation public long getTaskCount() { return taskCount; } @ManagedOperation public void setTaskCount(long taskCount) { this.taskCount = taskCount; } @ManagedOperation public long getCompletedTaskCount() { return completedTaskCount; } @ManagedOperation public void setCompletedTaskCount(long completedTaskCount) { this.completedTaskCount = completedTaskCount; } }
线程池状态监控类:ThreadPoolStatus,具体监控的指标如下:
其中corePoolSize、maximumPoolSize具体含义上文已经详细讲述,这里就不具体展开。
NettyRPC线程池监控JMX接口:ThreadPoolMonitorProvider,JMX通过JNDI-RMI的方式进行远程连接通讯,具体实现方式如下:
package com.newlandframework.rpc.parallel.jmx; import com.newlandframework.rpc.netty.MessageRecvExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.EnableMBeanExport; import org.springframework.jmx.support.ConnectorServerFactoryBean; import org.springframework.jmx.support.MBeanServerConnectionFactoryBean; import org.springframework.jmx.support.MBeanServerFactoryBean; import org.springframework.remoting.rmi.RmiRegistryFactoryBean; import org.apache.commons.lang3.StringUtils; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.MBeanException; import javax.management.InstanceNotFoundException; import java.io.IOException; /** * @author tangjie<https://github.com/tang-jie> * @filename:ThreadPoolMonitorProvider.java * @description:ThreadPoolMonitorProvider功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/13 */ @Configuration @EnableMBeanExport @ComponentScan("com.newlandframework.rpc.parallel.jmx") public class ThreadPoolMonitorProvider { public final static String DELIMITER = ":"; public static String url; public static String jmxPoolSizeMethod = "setPoolSize"; public static String jmxActiveCountMethod = "setActiveCount"; public static String jmxCorePoolSizeMethod = "setCorePoolSize"; public static String jmxMaximumPoolSizeMethod = "setMaximumPoolSize"; public static String jmxLargestPoolSizeMethod = "setLargestPoolSize"; public static String jmxTaskCountMethod = "setTaskCount"; public static String jmxCompletedTaskCountMethod = "setCompletedTaskCount"; @Bean public ThreadPoolStatus threadPoolStatus() { return new ThreadPoolStatus(); } @Bean public MBeanServerFactoryBean mbeanServer() { return new MBeanServerFactoryBean(); } @Bean public RmiRegistryFactoryBean registry() { return new RmiRegistryFactoryBean(); } @Bean @DependsOn("registry") public ConnectorServerFactoryBean connectorServer() throws MalformedObjectNameException { MessageRecvExecutor ref = MessageRecvExecutor.getInstance(); String ipAddr = StringUtils.isNotEmpty(ref.getServerAddress()) ? StringUtils.substringBeforeLast(ref.getServerAddress(), DELIMITER) : "localhost"; url = "service:jmx:rmi://" + ipAddr + "/jndi/rmi://" + ipAddr + ":1099/nettyrpcstatus"; System.out.println("NettyRPC JMX MonitorURL : [" + url + "]"); ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean(); connectorServerFactoryBean.setObjectName("connector:name=rmi"); connectorServerFactoryBean.setServiceUrl(url); return connectorServerFactoryBean; } public static void monitor(ThreadPoolStatus status) throws IOException, MalformedObjectNameException, ReflectionException, MBeanException, InstanceNotFoundException { MBeanServerConnectionFactoryBean mBeanServerConnectionFactoryBean = new MBeanServerConnectionFactoryBean(); mBeanServerConnectionFactoryBean.setServiceUrl(url); mBeanServerConnectionFactoryBean.afterPropertiesSet(); MBeanServerConnection connection = mBeanServerConnectionFactoryBean.getObject(); ObjectName objectName = new ObjectName("com.newlandframework.rpc.parallel.jmx:name=threadPoolStatus,type=ThreadPoolStatus"); connection.invoke(objectName, jmxPoolSizeMethod, new Object[]{status.getPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxActiveCountMethod, new Object[]{status.getActiveCount()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxCorePoolSizeMethod, new Object[]{status.getCorePoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxMaximumPoolSizeMethod, new Object[]{status.getMaximumPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxLargestPoolSizeMethod, new Object[]{status.getLargestPoolSize()}, new String[]{int.class.getName()}); connection.invoke(objectName, jmxTaskCountMethod, new Object[]{status.getTaskCount()}, new String[]{long.class.getName()}); connection.invoke(objectName, jmxCompletedTaskCountMethod, new Object[]{status.getCompletedTaskCount()}, new String[]{long.class.getName()}); } }
NettyRPC服务器启动成功之后,就可以通过JMX接口进行监控:可以打开jconsole,然后输入URL:service:jmx:rmi://127.0.0.1/jndi/rmi://127.0.0.1:1099/nettyrpcstatus,用户名、密码默认为空,点击连接按钮。
当有客户端进行RPC请求的时候,通过JMX可以看到如下的监控界面:
这个时候点击NettyRPC线程池各个监控指标的按钮,就可以直观的看到NettyRPC实际运行中,线程池的主要参数指标的实时监控。比如点击:getCompletedTaskCount,想查看一下目前已经完成的线程任务总数指标。具体情况如下图所示:
可以看到,目前已经处理了40280笔RPC请求。这样,我们就可以准实时监控NettyRPC线程池参数设置、容量规划是否合理,以便及时作出调整,合理的最大程度利用软硬件资源。
最后经过重构之后,NettyRPC服务端的Spring配置(NettyRPC/NettyRPC 2.0/main/resources/rpc-invoke-config-server.xml)如下:
xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:nettyrpc="http://www.newlandframework.com/nettyrpc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.newlandframework.com/nettyrpc http://www.newlandframework.com/nettyrpc/nettyrpc.xsd"> <context:property-placeholder location="classpath:rpc-server.properties"/> <nettyrpc:service id="demoAddService" interfaceName="com.newlandframework.rpc.services.AddCalculate" ref="calcAddService">nettyrpc:service> <nettyrpc:service id="demoMultiService" interfaceName="com.newlandframework.rpc.services.MultiCalculate" ref="calcMultiService">nettyrpc:service> <nettyrpc:registry id="rpcRegistry" ipAddr="${rpc.server.addr}" protocol="PROTOSTUFFSERIALIZE">nettyrpc:registry> <bean id="calcAddService" class="com.newlandframework.rpc.services.impl.AddCalculateImpl">bean> <bean id="calcMultiService" class="com.newlandframework.rpc.services.impl.MultiCalculateImpl">bean> beans>
通过nettyrpc:service标签定义rpc服务器支持的服务接口,这里的样例声明了当前的rpc服务器提供了加法计算、乘法计算两种服务给客户端进行调用。具体通过Spring自定义标签的实现,大家可以自行参考github:NettyRPC/NettyRPC 2.0/main/java/com/newlandframework/rpc/spring(路径/包)中的实现代码,代码比较多得利用到了Spring框架的特性,希望大家能自行理解和分析。
然后通过bean标签声明了具体加法计算、乘法计算接口对应的实现类,都统一放在com.newlandframework.rpc.services包之中。
最后通过nettyrpc:registry注册了rpc服务器,ipAddr属性定义了该rpc服务器对应的ip/端口信息。protocol用来指定,当前rpc服务器支持的消息序列化协议类型。
目前已经实现的类型有:JDK原生的对象序列化(ObjectOutputStream/ObjectInputStream)、Kryo、Hessian、Protostuff一共四种序列化方式。
配置完成rpc-invoke-config-server.xml之后,就可以启动RPC服务器Main函数入口:com.newlandframework.rpc.boot.RpcServerStarter。通过Maven打包、部署在(Red Hat Enterprise Linux Server release 5.7 (Tikanga) 64位系统,其内核版本号:Kernel 2.6.18-274.el5 on an x86_64),可以启动NettyRPC,如果一切正常的话,在CRT终端上会显示如下输出:
这个时候再进行客户端的Spring配置(NettyRPC/NettyRPC 2.0/test/resources/rpc-invoke-config-client.xml)。
xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:nettyrpc="http://www.newlandframework.com/nettyrpc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.newlandframework.com/nettyrpc http://www.newlandframework.com/nettyrpc/nettyrpc.xsd"> <context:property-placeholder location="classpath:rpc-server.properties"/> <nettyrpc:reference id="addCalc" interfaceName="com.newlandframework.rpc.services.AddCalculate" protocol="PROTOSTUFFSERIALIZE" ipAddr="${rpc.server.addr}"/> <nettyrpc:reference id="multiCalc" interfaceName="com.newlandframework.rpc.services.MultiCalculate" protocol="PROTOSTUFFSERIALIZE" ipAddr="${rpc.server.addr}"/> beans>
其中加法计算、乘法计算的demo代码如下:
package com.newlandframework.rpc.services; /** * @author tangjie<https://github.com/tang-jie> * @filename:Calculate.java * @description:Calculate功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public interface AddCalculate { //两数相加 int add(int a, int b); }
package com.newlandframework.rpc.services.impl; import com.newlandframework.rpc.services.AddCalculate; /** * @author tangjie<https://github.com/tang-jie> * @filename:CalculateImpl.java * @description:CalculateImpl功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class AddCalculateImpl implements AddCalculate { //两数相加 public int add(int a, int b) { return a + b; } }
package com.newlandframework.rpc.services; /** * @author tangjie<https://github.com/tang-jie> * @filename:Calculate.java * @description:Calculate功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public interface MultiCalculate { //两数相乘 int multi(int a, int b); }
package com.newlandframework.rpc.services.impl; import com.newlandframework.rpc.services.MultiCalculate; /** * @author tangjie<https://github.com/tang-jie> * @filename:CalculateImpl.java * @description:CalculateImpl功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class MultiCalculateImpl implements MultiCalculate { //两数相乘 public int multi(int a, int b) { return a * b; } }
值得注意的是客户端NettyRPC的Spring配置除了指定调用远程RPC的服务服务信息之外,还必须配置远程RPC服务端对应的ip地址、端口信息、协议类型这些要素,而且必须和RPC服务端保持一致,这样才能正常的进行消息的编码、解码工作。
现在我们就模拟1W个瞬时并发的加法、乘法计算请求,一共2W笔请求操作,调用远程RPC服务器上的计算模块,我们默认采用protostuff序列化方式进行RPC消息的编码、解码。注意,测试代码的样例基于1W笔瞬时并发计算请求,不是1W笔循环进行计算请求,这个是衡量RPC服务器吞吐量的一个重要指标,因此这里的测试样例是基于CountDownLatch进行编写的,类java.util.concurrent.CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。这里是加法计算RPC请求、乘法计算RPC请求,在RPC客户端分别先启动1W个线程,这个时候先挂起,然后等待请求信号,瞬时发起RPC请求。具体代码如下:
首先是加法计算并发请求类AddCalcParallelRequestThread:
package com.newlandframework.test; import com.newlandframework.rpc.services.AddCalculate; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; /** * @author tangjie<https://github.com/tang-jie> * @filename:AddCalcParallelRequestThread.java * @description:AddCalcParallelRequestThread功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class AddCalcParallelRequestThread implements Runnable { private CountDownLatch signal; private CountDownLatch finish; private int taskNumber = 0; private AddCalculate calc; public AddCalcParallelRequestThread(AddCalculate calc, CountDownLatch signal, CountDownLatch finish, int taskNumber) { this.signal = signal; this.finish = finish; this.taskNumber = taskNumber; this.calc = calc; } public void run() { try { //加法计算线程,先挂起,等待请求信号 signal.await(); //调用远程RPC服务器的加法计算方法模块 int add = calc.add(taskNumber, taskNumber); System.out.println("calc add result:[" + add + "]"); finish.countDown(); } catch (InterruptedException ex) { Logger.getLogger(AddCalcParallelRequestThread.class.getName()).log(Level.SEVERE, null, ex); } } }
其次是乘法计算并发请求类MultiCalcParallelRequestThread:
package com.newlandframework.test; import com.newlandframework.rpc.services.MultiCalculate; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; /** * @author tangjie<https://github.com/tang-jie> * @filename:MultiCalcParallelRequestThread.java * @description:MultiCalcParallelRequestThread功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class MultiCalcParallelRequestThread implements Runnable { private CountDownLatch signal; private CountDownLatch finish; private int taskNumber = 0; private MultiCalculate calc; public MultiCalcParallelRequestThread(MultiCalculate calc, CountDownLatch signal, CountDownLatch finish, int taskNumber) { this.signal = signal; this.finish = finish; this.taskNumber = taskNumber; this.calc = calc; } public void run() { try { //乘法计算线程,先挂起,等待请求信号 signal.await(); //调用远程RPC服务器的乘法计算方法模块 int multi = calc.multi(taskNumber, taskNumber); System.out.println("calc multi result:[" + multi + "]"); finish.countDown(); } catch (InterruptedException ex) { Logger.getLogger(MultiCalcParallelRequestThread.class.getName()).log(Level.SEVERE, null, ex); } } }
现在写出一个调用的测试客户端RpcParallelTest,测试RPC服务器的性能,以及是否正确计算出最终的结果。测试客户端RpcParallelTest的具体代码如下:
package com.newlandframework.test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import com.newlandframework.rpc.services.AddCalculate; import com.newlandframework.rpc.services.MultiCalculate; import org.apache.commons.lang3.time.StopWatch; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author tangjie<https://github.com/tang-jie> * @filename:RpcParallelTest.java * @description:RpcParallelTest功能模块 * @blogs http://www.cnblogs.com/jietang/ * @since 2016/10/7 */ public class RpcParallelTest { public static void parallelAddCalcTask(AddCalculate calc, int parallel) throws InterruptedException { //开始计时 StopWatch sw = new StopWatch(); sw.start(); CountDownLatch signal = new CountDownLatch(1); CountDownLatch finish = new CountDownLatch(parallel); for (int index = 0; index < parallel; index++) { AddCalcParallelRequestThread client = new AddCalcParallelRequestThread(calc, signal, finish, index); new Thread(client).start(); } signal.countDown(); finish.await(); sw.stop(); String tip = String.format("加法计算RPC调用总共耗时: [%s] 毫秒", sw.getTime()); System.out.println(tip); } public static void parallelMultiCalcTask(MultiCalculate calc, int parallel) throws InterruptedException { //开始计时 StopWatch sw = new StopWatch(); sw.start(); CountDownLatch signal = new CountDownLatch(1); CountDownLatch finish = new CountDownLatch(parallel); for (int index = 0; index < parallel; index++) { MultiCalcParallelRequestThread client = new MultiCalcParallelRequestThread(calc, signal, finish, index); new Thread(client).start(); } signal.countDown(); finish.await(); sw.stop(); String tip = String.format("乘法计算RPC调用总共耗时: [%s] 毫秒", sw.getTime()); System.out.println(tip); } public static void addTask(AddCalculate calc, int parallel) throws InterruptedException { RpcParallelTest.parallelAddCalcTask(calc, parallel); TimeUnit.MILLISECONDS.sleep(30); } public static void multiTask(MultiCalculate calc, int parallel) throws InterruptedException { RpcParallelTest.parallelMultiCalcTask(calc, parallel); TimeUnit.MILLISECONDS.sleep(30); } public static void main(String[] args) throws Exception { //并行度10000 int parallel = 10000; //加载Spring配置信息 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:rpc-invoke-config-client.xml"); //并发进行RPC加法计算、乘法计算请求 addTask((AddCalculate) context.getBean("addCalc"), parallel); multiTask((MultiCalculate) context.getBean("multiCalc"), parallel); System.out.printf("[author tangjie] Netty RPC Server 消息协议序列化并发验证结束!\n\n"); context.destroy(); } }
Netty RPC客户端运行情况,具体截图如下:下面是开始收到RPC服务器加法计算的结果截图。
好了,加法RPC请求计算完毕,控制台打印出请求耗时。
接着是调用RPC并行乘法计算请求,同样,控制台上也打印出请求耗时。
接着RPC的客户端运行完毕、退出,我们继续看下NettyRPC服务端的运行截图:
可以发现,NettyRPC的服务端确实都收到了来自客户端发起的RPC计算请求,给每个RPC消息标识出了唯一的消息编码,并进行了RPC计算处理之后,成功的把消息应答给了客户端。
经过一系列的模块重构,终于将NettyRPC重新升级了一下,经过这次重构工作,感觉自己对Netty、Spring、Java线程模型的了解更加深入了,不积跬步无以至千里,千里之行始于足下。学习靠的就是这样一点一滴的重复积累,才能将自己的能力提升一个台阶。
原创文章,加上本人才疏学浅,文笔有限,本文中有说得不对的地方,望各位同行不吝赐教。文中有忽略的地方希望读者可以补充,错误的地方还望斧正。
最后附上NettyRPC的开源项目地址:https://github.com/tang-jie/NettyRPC 中的NettyRPC 2.0项目。
感谢大家耐心阅读NettyRPC系列文章,如果本文对你有帮助,请点下推荐吧!
转载于:https://www.cnblogs.com/jietang/p/5983038.html