自从在园子里,发表了两篇如何基于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服务器设计经验谈) springboot 如何实现单点登录 lozhyf 工作面试学习springboot后端java 单点登录(SingleSign-On,SSO)允许用户使用一组凭证(如用户名和密码)登录到多个相关的应用系统中,而无需为每个系统单独进行身份验证。在SpringBoot中实现单点登录有多种方式,下面将分别介绍基于OAuth2.0和基于CAS(CentralAuthenticationService)这两种常见的实现方法。基于OAuth2.0实现单点登录1.项目依赖首先,在pom.xml中添加Spr 法律案例图像检索的前沿探索:基于AI的多模态搜索引擎设计【附保姆级代码】 一键难忘 精通AI实战千例专栏合集人工智能搜索引擎法律案例图像检索 本文收录于专栏:精通AI实战千例专栏合集https://blog.csdn.net/weixin_52908342/category_11863492.html从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。每一个案例都附带关键代码,详细讲解供大家学习,希望可以帮到大家。正在不断更新中文章目录法律案例图像检索的前沿探索:基于AI的多模态搜索引擎设 MySQL的Binlog解析 枯河垂钓 MySQL日志文件mysql服务器 查看当前主服务器(Master)的二进制日志(BinaryLog)状态SHOWMASTERSTATUS基于位点解析mysqlbinlog--start-position=4--stop-position=1248mysql-bin.000001>/data/01.sql基于时间解析mysqlbinlog--start-datetime="2025-02-2321:00:00"/data/mysql 如何了解和学习“已知的未知” 由数入道 学习 了解和学习“已知的未知”(KnownUnknowns)是系统性知识管理的关键环节,需要建立结构化认知体系。以下提供融合认知科学、教育心理学和系统工程学的完整方法论,包含6大核心模块、28项具体技术,并配备量化指标和工具链。一、知识缺口识别系统1.认知地形测绘理论模型:基于VanMerriënboer的“四要素教学设计模型”(4C/ID)实施方法:知识域边界标定:使用领域本体论工具(Protege) 模型算力需求估算 由数入道 人工智能 计算模型的算力需求,通常基于模型的参数量(BillionParameters,简称B)和训练/推理的计算任务复杂度,结合硬件计算能力(例如每秒浮点运算次数,FLOPS)来估算。以下是详细的方法和公式说明,以及实际的计算示例。1.算力需求的基本公式1.1训练阶段训练阶段的算力需求可以通过以下公式估算:训练算力需求(FLOPs)=2×P×N×S×D\text{训练算力需求(FLOPs)}=2\time Spring Boot 3 集成 RabbitMQ 实践指南 翱翔-蓝天 java-rabbitmqspringbootrabbitmq SpringBoot3集成RabbitMQ实践指南1.RabbitMQ核心原理1.1什么是RabbitMQRabbitMQ是一个开源的消息代理和队列服务器,使用Erlang语言开发,基于AMQP(AdvancedMessageQueuingProtocol)协议实现。它支持多种消息传递模式,具有高可用性、可扩展性和可靠性等特点。1.2核心概念1.2.1基础组件Producer(生产者)消息的发送者 使用 Python + Tinify 高效批量压缩图片,优化 Webpack 打包速度! 程序员小续 java开发语言pythonreact.jswebpack前端前端框架 webpack本身可以压缩图片image-webpack-loader,但是打包时间长,图片是有损压缩为了图片质量采用Pythontinify库脚本压缩以下是一个基于Python的Tinify(TinyPNG)库的图片压缩脚本,它可以递归压缩指定目录下的所有JPG、PNG和WebP图片,并统计压缩前后的总大小及节省的空间。一、代码功能遍历目录及其子目录,查找jpg、png、webp图片文件使用Ti Pytest教程:Pytest学习前置知识 旦莫 Pytest教程pytest学习python Pytest是一个基于Python编写的全功能测试框架,它被广泛应用于软件开发领域的自动化测试。在学习Pytest之前,我们需要掌握一些基础知识。在本篇技术博客中,我们将讨论这些基础知识,以及如何学习Pytest。一、Python基础知识在使用Pytest之前,您需要掌握Python编程语言的基础知识。这包括Python的语法、数据类型、流程控制、函数和模块等内容。语法:Python具有简洁而清晰 conda基本命令 struggilr condalinux人工智能 1.基于python3.8.3创建名为test的conda环境condacreate--nametestpython=3.8.82.查看本机已经创建了哪些conda环境condainfo-envs3.激活环境activatetest4.查看此时虚拟环境下已安装的包condalist5.安装XXX包condainstallXXX6.删除XXX包condaremoveXXX7.列出所有环境condae 系统架构设计:软件工程部分知识概述 Dola_Pan 系统架构设计软件工程大数据 软件工程:构建高质量软件的基石在当今数字化时代,软件已渗透到我们生活的方方面面,从日常的手机应用到复杂的工业控制系统,软件的质量和性能直接影响着用户体验和业务发展。而软件工程,作为一门研究和应用如何高效、可靠地开发软件的学科,对于打造优质软件产品起着至关重要的作用。本文将带你深入了解软件工程的关键知识领域,探索其在实际项目中的应用与价值,并结合示例代码进行讲解。一、软件工程概述软件工程旨在采用工程 飞天侠:用 aioredis 加速你的 Redis 操作 星际编程喵 Python探索之旅redis数据库缓存python 前言如果你还在用同步方式操作Redis,你的应用可能还停留在“慢跑”阶段,而不是极速奔跑!在现代高性能应用中,响应速度至关重要,而异步操作就是那把解锁高速的钥匙。而aioredis,这款基于asyncio的Redis异步客户端,正是帮你提升性能、缩短延迟的得力助手。它能让你像开跑车一样,秒杀同步操作的瓶颈,快速处理海量请求。今天,我们就来一起揭秘这款神奇工具,看看如何通过aioredis加速你的R 基于双层优化的微电网系统规划设计方法(Matlab代码实现) 然哥爱编程 matlab开发语言 个人主页欢迎来到本博客❤️❤️博主优势:博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳️座右铭:行百里者,半于九十。本文目录如下:目录1概述1.1微电网系统结构1.2微电网系统双层规划设计结构1.3双层优化模型1.4上层容量优化模型1.5下层调度优化模型2运行结果3文献来源4Matlab代码、数据、文章1概述微电网系统可将多种类型的分布式发电单元组合在一起,有效发挥单一能源系统的优点,实现 点云从入门到精通技术详解100篇-基于多线激光雷达的点云数据处理与导航(续) 格图素书 人工智能算法 目录三维点云建图与定位算法研究§3.1激光SLAM技术§3.2基于特征的建图算法§3.2.1三维点云建图算法简述§3.2.3LeGO-LOAM建图算法§3.3基于点云配准的定位算法§3.3.1点云配准§3.3.2基于ICP的配准定位算法§3.3.3基于NDT的配准定位算法§3.4基于LM法优化的NDT配准定位算法§3.4.1列文伯格-马夸尔特法原理§3.4.2LM-NDT算法配准原理及流程§3.5 MTTK Vue Wrap 应用场景详解(二) MTTKbelinda VUEWrapVUE组件封装vue.js开源javascript低代码 关键字:Vue3,配置开发,快速开发,组件封装,低代码前文提要:探讨了基于配置文件渲染出表单的具体应用场景以及和SFC的对比优势。‒代码灵活,容易实现.函数在循环分支相对于SFC有一定优势,而递归调用等优势则非常明显.函数希望输出结果是JSON(JS对象),自然支持用函数生成.‒组合使用.上述表单配置可以组合起来实现更加复杂功能,譬如实现输入表单后点击查询按钮查询,并把查询结果显示在表格中.所以一 【2025年】全国CTF夺旗赛-从零基础入门到竞赛,看这一篇就稳了! 网安詹姆斯 web安全CTF网络安全大赛pythonlinux 【2025年】全国CTF夺旗赛-从零基础入门到竞赛,看这一篇就稳了!基于入门网络安全/黑客打造的:黑客&网络安全入门&进阶学习资源包目录一、CTF简介二、CTF竞赛模式三、CTF各大题型简介四、CTF学习路线4.1、初期1、html+css+js(2-3天)2、apache+php(4-5天)3、mysql(2-3天)4、python(2-3天)5、burpsuite(1-2天)4.2、中期1、S 核心团队来自百度,大模型AI Agents创业团队招聘啦! datawhale DatawhaleDatawhale分享初创公司:浮点奇迹,方向:AIAgents**团队简介我们是浮点奇迹团队,一个AIAgents赛道初创公司,创始团队主要来自百度的AI、搜索核心算法部门,有业界领先的大模型自研能力和十亿规模平台型C端产品研发能力;我们专注打造AIAgents原生的互联网内容平台,我们的长期愿景是重新定义互联网的信息生产和分发,加速高价值长尾信息的流动和传播。目前,我们正在寻 基于springboot断点续传或分片上传 小码农叔叔 springboot相关springboot入门到精通java断点续传或分片上传java断点续传 前言在做文件上传,尤其是大文件上传过程中,比如大视频等,经常会碰到这么一种情况,就是用户希望这一次没有上传完,或者中途因为网络原因上传失败了,下一次继续上传的时候可以接着上次没有传完的地方继续上传,这样既可以保证上传的进度,又不用重新上传,避免耗时的等待,这个需求该怎么实现呢?思路分析大体来说,可以分为下面几步进行思考对于前端页面来说,考虑到一次上传大文件后端可能承受不住压力,就需要将文件进行分段 五原则四实践,REST API安全性请谨记 云原生和微服务架构等技术的流行让API受到越来越大的重视。那么当应用程序开始上云,各项需求都可以通过云服务满足的情况下,应用程序,尤其基于云端API的应用程序的安全问题该如何解决?表现层状态转移(REST,RepresentationalStateTransfer)这种软件架构风格最早可追溯到计算机科学家RoyFielding于2000年发布的一篇博士论文。在此后多年里,RESTAPI(有时也称为 RocketMq学习笔记 花开不识君 java中间件rocketmq RocketMq学习笔记本文记录作者基于RocketMq4.9x版本对RocketMq部分功能特性的学习,并尝试从源码角度分析其实现原理。相关文章RocketMq5.0proxy的引入:https://juejin.cn/post/7293788137662758946RocketMqDocker集群搭建:https://www.cnblogs.com/xiao987334176/p/167718 django-hexo-admin(一个基于django的hexo博客平台) 朝凡FR 个人项目djangovue django-hexo-admin一个过分简单的前后端分离的基于django、vue3的hexo博客平台后台管理系统1.项目介绍一个异常简单的但理论上能够完全保留hexo生态的博客后台管理系统。仅仅对hexo的source以及输出路径public做出了一定的硬性调整,所以如果你很熟悉hexo,那么你将非常轻松的使用这套系统进行hexo配置和hexo主题配置的录入、修改、应用。并非常方便的进行博客的 基于企业架构视角建模 fajianchen IT架构建模工具系统设计架构数据模型 基于企业级架构视角的模型是以企业战略为核心,依托业务模型建立企业级数据标准,贯穿战略层面与系统应用层面。基于企业级架构视角的模型建设有助于打破组织内IT系统的孤岛式建设,核心任务是对银行内战略的分解、传导,帮助企业将零散的能力“聚沙成塔”,确保战略意图和绩效指标逐层落实到每一个流程步骤、程序模块、操作规范中。企业级架构中的业务模型从产品、流程及数据三个角度,对企业的业务运营进行精细化、结构化的描述 探索与Cursor协作创建一个完整的前后端分离的项目的最佳实践 朝凡FR 个人项目人工智能软件工程个人开发 探索与Cursor协作创建一个完整的前后端分离的项目的最佳实践Cursor简介Cursor在目前代表了AI编程技术的顶峰。在一定程度上可以说是当今AI时代的最强生产力代表。为此,不惜重金开了年费会员来紧跟时代步伐。当然cline、roocode、trae等开源或者免费产品也在紧追不舍。Cursor是一款基于VisualStudioCode(VSCode)开发的AI驱动代码编辑器,旨在通过集成先进的 基于大模型的 SDL 需求阶段安全需求挖掘实战指南 —— 四步法实现从业务需求到风险矩阵的智能转换 大F的智能小课 大模型理论和实战人工智能语言模型算法安全 在软件开发生命周期(SDL)中,需求阶段的安全需求挖掘至关重要,它直接影响到软件的安全性和可靠性。随着大模型技术的发展,我们可以利用其强大的自然语言处理和知识图谱能力,实现从业务需求到风险矩阵的智能转换。本文将介绍一种基于大模型的四步法,帮助安全团队高效挖掘安全需求。一、业务需求解析:大模型驱动的语义理解目标:将自然语言描述的业务需求转化为结构化安全要素。方法:需求文本预处理:使用大模型(如GPT 适用于呼叫中心质检的离线ASR模型 狂爱代码的码农 VOIP那些事容器 以下是适用于中文呼叫中心质检的离线语音转文字(STT)模型及工具,根据性能、中文支持、部署灵活性等维度整理:1.开源模型与框架1.1WeNet(出门问问&西北大学)特点:端到端语音识别框架,专为中文优化,支持流式和非流式识别,适合工业场景。优势:预训练模型基于AIShell等中文数据集,准确率高。低延迟,适合实时处理(如质检中的实时监控)。支持GPU/CPU部署,提供Python和C++接口。部署 探索未来之声:趣玩语音识别新篇章——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,方便开发 二分查找排序算法 周凡杨 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