当前位置: 首页 > news >正文

久久室内设计网广州seo顾问服务

久久室内设计网,广州seo顾问服务,上海整站优化公司,wordpress主题零基础为什么需要池化技术 系统运行时必然是需要数据库连接、线程等一些重量级对象#xff0c;频繁的创建这种对象对性能有着不小的开销#xff0c;所以为了减少没必要的创建和开销#xff0c;我们就用到了池化技术。 通过创建一个资源池来保存这些资源便于后续的复用#xff0c…为什么需要池化技术 系统运行时必然是需要数据库连接、线程等一些重量级对象频繁的创建这种对象对性能有着不小的开销所以为了减少没必要的创建和开销我们就用到了池化技术。 通过创建一个资源池来保存这些资源便于后续的复用从而提升系统性能。 池化技术实现的三种方式 一般来说使用池化技术的客户端有以下三种形式: xxxPool和xxxConnection 这种池化方式的实现即我们从xxxPool获取到xxxConnection对象我们在使用xxxConnection对象之后必须手动调用某些API将资源归还否则可能会导致内存泄漏之类的问题。并且xxxConnection对象一般都是非线程安全的多线程操作这类对象很可能导致一些意外情况发生。 xxxClient和xxxPool xxxClient这种形式用法一般都是从xxxPool获取这个对象我们使用完成后无需手动将资源归还xxxClient会自动完成这种操作一般来说这种对象都是线程安全的。 xxxConnection对象 xxxConnection就是一个单独的连接对象是一个完全的单一连接对象不仅性能一般还需要手动获取和释放使用不当极可能导致线程安全问题。 Jedis使用不当导致的线程安全问题 前置步骤 在完成这个实验之前我们必须完成对实验环境的搭建首先肯定是下载并启动redis服务端由于笔者使用的是Windows系统所以这里就简单的下载了一个Windows环境下的redis。下载地址为: https://github.com/tporadowski/redis/releases 首先自然是在spring boot项目中引入jedis的依赖 !--jedis依赖--dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion3.2.0/version/dependency完成这些步骤之后我们编写一个简单的测试用例测试一下连通性 public class JedisTest {private static Logger logger LoggerFactory.getLogger(JedisTest.class);/*** 测试ping*/Testpublic void testPing() {Jedis jedis null;try {jedis new Jedis(127.0.0.1);String result jedis.ping();Assert.isTrue(PONG.equals(result),unable to connect redis);} finally {jedis.close();}}} 从输出结果来看环境准备完成了接下来就可以开始实验了。 问题重现 为了方便实验我们首先基于redis客户端往redis中设置两个值分别是是(a,1)和(b,2) try (Jedis jedis new Jedis(127.0.0.1, 6379)) {Assert.isTrue(OK.equals(jedis.set(a, 1)), set a1 return ok);Assert.isTrue(OK.equals(jedis.set(b, 2)), set b2 return ok);}然后我们再编写下面这样一段代码可以看到两个线程都是用同一个redis客户端获取对象线程1获取a的值如果不为1则输出一段警告。线程不断获取b的值如果b的value不为2则也输出一段警告。 GetMapping(wrong)public void wrong() throws InterruptedException {Jedis jedis new Jedis(127.0.0.1);CountDownLatch countDownLatch new CountDownLatch(2);new Thread(() - {for (int i 0; i 1000; i) {String result jedis.get(a);if (!1.equals(result)) {log.warn(expect to be 1 but found {}, result);return;}}countDownLatch.countDown();}, t1).start();new Thread(() - {for (int i 0; i 1000; i) {String result jedis.get(b);if (!2.equals(result)) {log.warn(expect to be 2 but found {}, result);return;}}countDownLatch.countDown();}, t2).start();countDownLatch.await();}我们将项目进行启动测试并测试该接口可以看到该接口报错了。 经过笔者不断点击调试获取到的a的值变为2出现了线程安全问题。 阅读源码了解究竟 我们的问题发生在get所以我们不妨从get方法中查看究竟。 从源码中可以看到get方法获取值是通过一个client对象的get方法获取的我们不妨步入看看具体逻辑。 Overridepublic String get(final String key) {checkIsInMultiOrPipeline();client.get(key);return client.getBulkReply();}可以看到client的get方法还是通过一个get方法完成value的获取我们再步入看看 Overridepublic void get(final String key) {get(SafeEncoder.encode(key));}再次步入后我们来到了BinaryClient类的get方法它的逻辑是发送一个get的命令以key作为参数来完成值的获取的我们不妨在步入看看细节。 public void get(final byte[] key) {sendCommand(GET, key);}最终我们来到了Connection对象了可以看到在多线程情况下多个线程获取的值会写到同一个outputStream中这就是造成我们线程安全的原因所在。 //Connection共用一个outputStream对象 private RedisOutputStream outputStream;public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {try {//建立连接connect();//向服务端发送cmd命令参数为args将结果写入outputStream中Protocol.sendCommand(outputStream, cmd, args);} catch (JedisConnectionException ex) {broken true;throw ex;}}除了这一处之外还有一个地方我们回到get方法可以看到return语句后面跟着一个getBulkReply方法我们步入看看。 Overridepublic String get(final String key) {checkIsInMultiOrPipeline();client.get(key);return client.getBulkReply();}可以看到redis的结果是从getBinaryBulkReply中获取再通过SafeEncoder.encode转码返回给客户端的所以我不妨看看getBinaryBulkReply是如何完成值的获取的。 public String getBulkReply() {final byte[] result getBinaryBulkReply();if (null ! result) {return SafeEncoder.encode(result);} else {return null;}}重点来了可以看到结果是通过flush将结果写入某个地方然后再通过readProtocolWithCheckingBroken返回的我们再次步入看看flush做了什么。 public byte[] getBinaryBulkReply() {flush();return (byte[]) readProtocolWithCheckingBroken();}可以看到这个flush方法就是将上文的outputStream的值刷到缓冲区中试想一下我们线程1执行完flush就被挂起然后线程2执行flush的话线程1是不是就可以拿到线程2的结果呢 protected void flush() {try {outputStream.flush();} catch (IOException ex) {broken true;throw new JedisConnectionException(ex);}}基于debug剖析原因 为了印证上文中说到的线程1调用flush后线程2再次flush将导致线程1取到线程2的猜想我们不妨通过debug的方式来重现这个问题。 我们不妨在jedis的get方法上使用thread模式打个断点。 然后启动项目开始debug首先将线程1执行到flush后切换到线程2 然后让线程2也执行完flush并将readProtocolWithCheckingBroken走完让结果存到inputStream中。 最后我们将线程切回线程1可以看到key为a却得到了2线程安全问题重现。 最终代码输出了警告 解决方案 从源码中我们可以看出每一个jedis都相当于一个connection对象它是属于我们说的第一种连接池从pool中获取xxxConnection的情况获取后需要我们手动归还。连接池声明代码如下 private static JedisPool jedisPool new JedisPool(127.0.0.1, 6379); 因为jedisPool中获取的都是jedis对象我们想了解一下为什么无论连接池还是单jedis对象都能通过close方法关闭或者归还对象我们查看了一下close方法: 可以看到源码中又做了判断如果是线程池中的对象则调用returnBrokenResource或returnResource归还如果是我们自己创建的Jedis对象则调用close关闭资源。 Overridepublic void close() {//如果是线程池中的对象则调用returnBrokenResource或returnResource归还if (dataSource ! null) {JedisPoolAbstract pool this.dataSource;this.dataSource null;if (client.isBroken()) {pool.returnBrokenResource(this);} else {pool.returnResource(this);}} else {//如果是我们自己创建的Jedis对象则调用closesuper.close();}}为了保证项目关闭之后连接池能够正确关闭我们可以使用PostConstruct确保在bean完成依赖注入之后添加一个销毁jedisPool 的钩子方法。 PostConstructpublic void init() {Runtime.getRuntime().addShutdownHook(new Thread(()-{jedisPool.close();}));}然后就可以修改我们的接口了这样一来每一个线程使用的都是从连接池中获取的对象因为Jedis继承了Closeable所以我们可以用JDK的try语法完成close逻辑。就不会有线程安全问题了。 GetMapping(right)public void right() throws InterruptedException {CountDownLatch countDownLatch new CountDownLatch(2);new Thread(() - {try (Jedis jedisjedisPool.getResource()) {for (int i 0; i 1000; i) {String result jedis.get(a);if (!1.equals(result)) {log.warn(expect to be 1 but found {}, result);return;}}countDownLatch.countDown();}}, t1).start();new Thread(() - {try (Jedis jedisjedisPool.getResource()) {for (int i 0; i 1000; i) {String result jedis.get(b);if (!2.equals(result)) {log.warn(expect to be 2 but found {}, result);return;}}countDownLatch.countDown();}}, t2).start();countDownLatch.await();}小结 从本例子中可以看到笔者使用池化技术时的几个注意点: 根据源码推测池化技术的设计方式和使用方法。基于多线程模式debug排查和印证并使用池化技术解决问题。时刻保持对池化资源的归还和关闭工作。 使用和不使用池化技术性能之间的差距 问题简介 上文我们提到了池化技术的重要性接下来我们就基于apache客户端来了解一下不同的池化技术运用方式之间的差距。 前置步骤 为了更好的完成实验我们可以必须在服务器中安装一个轻量级压测工具wrk读者可以参考笔者写的这篇文章: 极简的wrk安装和使用教程 为了更好查看池化技术之间网络交互过程我们同样还需要安装一下Wireshark读者可以参考这篇文章 网络分析工具——WireShark的使用超详细 完成好这些步骤之后我们就可以开始进行实践了首先打开我们的spring boot项目(端口号设置为45678)引入httpclient的依赖: !--httpclient依赖--dependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactIdversion4.5.13/version/dependency错误的池化使用示例 先来看看错误的示例可以看到笔者的使用方式可以说是非常低级的每次请求进来都去创建一个连接池然后从连接池中获取一个连接调用test接口。 GetMapping(wrong2)public String wrong2() {//每次请求进来都创建一个连接池try (CloseableHttpClient client HttpClients.custom()//连接池的连接数为1.setMaxConnTotal(1).setConnectionManager(new PoolingHttpClientConnectionManager()).evictIdleConnections(60, TimeUnit.SECONDS).build();//使用连接池中的连接调用同一个项目下的test方法CloseableHttpResponse response client.execute(new HttpGet(http://127.0.0.1:45678/httpclientnotreuse/test))) {return EntityUtils.toString(response.getEntity());} catch (Exception e) {e.printStackTrace();}return null;}GetMapping(/test)public String test() {return OK;}编写完成之后我们不妨在服务器中将项目启动并使用wrk进行压测。 如下所示我们使用一个线程和一个连接对接口压测10s。 wrk -t1 -c1 -d10s --latency http://localhost:45678/httpclientnotreuse/wrong2压测结果如下可以看到qps为414.01。 Running 10s test http://localhost:45678/httpclientnotreuse/wrong21 threads and 1 connectionsThread Stats Avg Stdev Max /- StdevLatency 2.49ms 1.23ms 12.12ms 89.16%Req/Sec 415.93 88.45 620.00 70.00%Latency Distribution50% 2.06ms75% 2.96ms90% 3.80ms99% 7.65ms4144 requests in 10.01s, 466.15KB read Requests/sec: 414.01 Transfer/sec: 46.57KB 然后我们再通过服务器进行抓包了解一下压测通信过程详情。 如下命令所示笔者使用tcpdump对45678端口(即我们的web项目端口)进行抓包并将结果写到wrong2.pcap文件中。 tcpdump -i any tcp and port 45678 -w wrong2.pcap开启抓包命令之后我们再打开一个终端对接口进行压测完成后将pcap文件下载到本地用Wireshark打开。 如下所示我们在Wireshark过滤一栏键入下面的指令。 tcp.port 45678 http并将源端口号应用为列以便观察数据。 可以看到每一个请求test接口的源端口号都不一样很明显每次发起请求时都是发起test请求时都进行了TCP三次握手再请求的过程。 我们不妨将过滤条件删除可以看到抓包记录中大量的SYN包很明显大量的HTTP请求都是先进行TCP三次握手了。 正确的池化使用示例 接下来我们再来看看正确池化的效果。首先我们会在类中声明一个全局静态连接池 private static CloseableHttpClient client null;static {client HttpClients.custom().setMaxConnTotal(1).setConnectionManager(new PoolingHttpClientConnectionManager()).evictIdleConnections(60, TimeUnit.SECONDS).build();Runtime.getRuntime().addShutdownHook(new Thread(() - {try {client.close();} catch (IOException e) {e.printStackTrace();}}));}然后我们的连接池则是直接使用这个连接池发起http请求的。 GetMapping(right)public String right() {try (CloseableHttpResponse response client.execute(new HttpGet(http://127.0.0.1:45678/httpclientnotreuse/test))) {return EntityUtils.toString(response.getEntity());} catch (Exception e) {e.printStackTrace();}return null;}然后我们使用同样的参数对这个接口进行压测 wrk -t1 -c1 -d10s --latency http://localhost:45678/httpclientnotreuse/right从压测结果来看qps为3337.56很明显正确的使用池化技术对性能是有着质的提高。 Running 10s test http://localhost:45678/httpclientnotreuse/right1 threads and 1 connectionsThread Stats Avg Stdev Max /- StdevLatency 764.22us 4.52ms 82.06ms 98.54%Req/Sec 3.37k 478.07 4.16k 73.00%Latency Distribution50% 274.00us75% 316.00us90% 379.00us99% 15.98ms33637 requests in 10.08s, 3.70MB read Requests/sec: 3337.56 Transfer/sec: 375.44KB 我们使用同样的方式进行抓包 tcpdump -i any tcp and port 45678 -w right.pcap从抓包记录来看很明显发起http请求的接口都是47402都是复用同一个TCP连接。 小结池化技术性能压测实验 这里我们通过wrk压测和Wireshark抓包对池化技术使用细节进行了进一步了解很明显正确的使用池化技术可以复用资源避免资源没必要的创建和销毁进而提高服务器每秒处理请求数即QPS。 池化配置问题 最大连接数的配置可能造成的问题 既然聊到池化技术那么我们就来了解一下池化技术中关于连接池的配置问题。我们使用连接池时都会设置连接池参数的配置这里面可能会涉及一个比较常用的配置最大连接数。 这个参数我们必须谨慎如果连接参数过小极可能导致大量客户端等待连接导致吞吐量下降。 而连接数设置过大又可能会增加服务端维护连接池的开销以及大量客户端通过这些连接和服务端建立远程交互进而造成大量线程切换的开销。 基于数据库连接池复现问题 我们不妨基于spring boot jpa写一段保存用户信息的功能来模拟一下数据库连接池被打满的情况。 首先我们引入JPA和MySQL的依赖。 !-- jpa --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- myql驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependency建立一张user表 -- wiki.user definitionCREATE TABLE user (id char(8) NOT NULL DEFAULT COMMENT id,login_name varchar(50) NOT NULL COMMENT 登陆名,name varchar(50) DEFAULT NULL COMMENT 昵称,password char(32) NOT NULL COMMENT 密码,pass_word varchar(255) DEFAULT NULL,PRIMARY KEY (id),UNIQUE KEY login_name_unique (login_name) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT用户;编写user表对应的实体类 Entity Data public class User {IdGeneratedValue(strategy AUTO)private Long id;private String name;private String loginName;private String password; } 编写user表持久层代码代码很简单集成JPA类即可具体的逻辑该类都为我们实现了我们只需指定泛型即可。 Repository public interface UserRepository extends JpaRepositoryUser, Long { } 服务层代码如下所示逻辑很简单创建一个实体类调用持久层UserRepository 保存即可注意我们为了模拟真实环境的某些复杂业务这个功能我们加了事务并且还让其休眠500ms。 Service public class UserService {Autowiredprivate UserRepository userRepository;Transactionalpublic User register() {User user new User();user.setName(new-user- System.currentTimeMillis());user.setLoginName(new-user- System.currentTimeMillis());user.setPassword(123);userRepository.save(user);try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}return user;}} 最后是控制层代码。 Autowiredprivate UserService userService;GetMapping(test)public Object test() {return userService.register();}因为我们后续会用到jconsole所以我们会在配置文件中添加下面这段配置使得我们可以在jconsole的mbean中看到这个信息。 spring.datasource.hikari.register-mbeanstrue为了保证文章完整性这里也把数据库连接配置贴上: spring.jpa.show-sqltrue spring.jpa.hibernate.ddl-autoupdate spring.datasource.urljdbc:mysql://xxxxxxx spring.datasource.username xxxxxxx spring.datasource.password xxxx spring.datasource.platformmysql spring.jpa.open-in-viewfalse然后我们会将这个项目打包到服务器上为了能让本地jconsole监控到该服务信息我们启动时会添加下面几个参数 java -Djava.rmi.server.hostnamexxxx #远程服务器ip -Dcom.sun.management.jmxremote #允许JMX远程调用 -Dcom.sun.management.jmxremote.port3214 #允许JMX远程调用的端口号后续我们的jmx就可以通过该ip和该端口和应用建立连接 -Dcom.sun.management.jmxremote.sslfalse # 是否开启ssl -Dcom.sun.management.jmxremote.authenticatefalse # 是否开启ssl-jar java-common-mistakes.jar项目启动后我们可能还需要进行这样一步操作以笔者为例项目启动时JVM远程调用端口号为3214但是放行该端口后还是无法连接查阅网上资料了解到还有别的端口需要放行所以我们需要键入下面这条命令 netstat -apn |grep java最终输出这样结果可以看到除了应用端口和JMX远程调用端口以外还有34751和36709两个端口所以我们一并将其放行。 完成后我们就可以使用jconsole进行远程连接了打开JConsole输入应用的ipJMX远程调用端口号即可以笔者为例则是:i ip:3214接下来我们就可以在JConsole中看到连接池的信息了可以看到默认情况下最大连接数为10: 我们不妨基于wrk进行一次压测,因为笔者的服务器为双核所以使用了20个线程进行压测: wrk -t20 -c20 -d30s --latency http://localhost:45678/improperdatasourcepoolsize/test可以看到在现有连接池都被占用大量连接处于等待状态导致大量请求报错: 报错内容基本都为超时 unable to obtain isolated JDBC connection] with root causejava.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms. 解决思路 所以我们需要对连接池进行调整为了直观压测获取服务器最大并发数我们将连接池最大值调整为50: spring.datasource.hikari.maximum-pool-size50然后再次进行压测最终我们得到服务器的最大并发数为30为了保证有一半的冗余连接池数量调整为并发数*2即60 小结一下对于连接池参数问题建议做到以下几点: 做好配置确保可以实时监控确保自己的配置是生效的。进行压测确保配置参数符合预期效果。配置连接参数要确保留有一半的余量并保证我们的监控工具能够在剩余不到一半的情况下发出预警。 参考文献 Redis开发与运维 Java 业务开发常见错误 100 例 wireshark添加列端口号等 JConsole连接远程服务器方法 使用JConsole链接远程服务器 jconsole远程连接失败
http://www.huolong8.cn/news/419072/

相关文章:

  • 门图书馆户网站建设方案网站站点怎么做
  • 做数码测评的网站搭建平台 能说会写
  • 在线注册个体工商户网站改版 seo
  • 淘宝网站的建设与运营设计思路做公司网站和设计logo
  • 安徽城乡建设部网站首页个人网站有什么缺点
  • 长沙做网站品牌徐州人才招聘网官网
  • 手机网站产品展示模板宁波 外贸网站建设
  • 卓越高职院建设网站开发一个app要多少钱呢
  • 企业邮箱 网站建设qq建设网站首页
  • 找工作去哪个网站合肥网站seo公司
  • 网站特效 站长交友wordpress
  • 制作网站怎么做的代运营服务
  • 上海做网站的的公司wordpress 媒体播放
  • 免费网站建设平台刚刚廊坊发生大事了
  • 黄浦品牌网站建设app网站开发重庆
  • 大庆门户网站郑州石凡平面设计有限公司
  • 天津网站建设定做秦皇岛营销式网站制作
  • 可做笔记的阅读网站wordpress jquery 插件
  • 图片翻转插件wordpress网站建设seo视频
  • 专业网站给个网站谢谢
  • 陕西省住房和城乡建设部网站官网什么浏览器适合看网站
  • 已经有域名 怎么做网站基因数据库网站开发价格
  • 下载软件的网站wordpress文章
  • 名词解释 网站内容上海网站设计开
  • 重庆有网站公司怎样做企业网站备案
  • 做设计在哪个网站接单如何套用wordpress的源码
  • 摩托车网站建设建站能赚钱吗
  • 电影宣传网站模板免费下载知道ip怎么查域名
  • 变更网站怎么做thinkphp2.1网站挂文件
  • 如何用ps做网站网页做影视网站引流