2017-2018工作总结(更新中)
〇、序
今年3月底正式向公司提出了辞职申请,在4月底完成了工作交接正式离职了。
作为第一份工作不得不说tbj算是很不错了,不限制的上班时间,平等的工作环境,年轻的同事,业内流行的技术栈,今年年初还入选杭州独角兽企业作为政府帮扶企业了,希望以后的tbj越来越好吧。
以后可能会去导师的公司做sdk开发相关的工作,Java可能都不太用了,因为记性一直不太好,所以准备列个清单总结一下两年(+实习时间)以来的实践经验(不涉及技术原理),以后假如再回归Java开发不至于像一张白纸:)
一、技术栈
1、Dubbo
分布式服务发布订阅中心,使用到的配置项非常简单。
1 <dubbo:application name="myProject"/> 2 <dubbo:registry server="zkRegistry" protocol="${dubbo.registry.protocol}" address="${dubbo.registry.address}" file="${dubbo.registry.file} " /> 3 <!-- 4 <dubbo:monitor address="${dubbo.monitor.address}" /> 5 --> 6 <dubbo:protocol port="${dubbo.protocol.port}" /> 7 <dubbo:provider timeout="${dubbo.provider.timeout}" retries="0"/> 8 <!-- 关闭启动时检查 否则在服务发布方不可用时会得到null对象 抛异常 --> 9 <dubbo:consumer check="false" />
发布与订阅服务的配置:
1 <!-- 服务订阅 --> 2 <dubbo:reference id="xxFacade" interface="com.cyan.project.facade.xxFacade" version="${project.dubbo.version}" /> 3 4 <!-- 服务发布 --> 5 <dubbo:service ref="xxFacadeImpl" interface="com.cyan.project.facade.xxFacade" version="${project.dubbo.version}"/>
在测试环境使用时可能会出现有开发人员将本地机器注册到服务中心的情况,我的做法是将本地发布服务的版本号改掉(从1.0改到77),在别人干这种事的时候可以登陆dubbo控制台禁用掉除测试环境外的其他服务IP。
在调用下层服务请求数据库数据的接口时,应参考数据量大小尽量做成分页的,数据量太大一是容易调用超时,二也有可能序列化数据太长撑爆了dubbo消息体(4byte)。
2、RocketMQ
分布式消息系统。什么时候使用dubbo接口什么时候使用消息呢?在我理解中消息主要应用在下面几个场景里:
(1)上层应用通信。相对于暴露服务的下层应用,上层应用在彼此通讯的时候不会再通过dubbo暴露接口,否则很容易出现循环依赖的问题。这个时候想要进行上层应用之间的通信就可以通过消息来实现,还有一种实现方法是参考了代码设计模式中的桥接模式,创建一个bridge项目来实现两个应用的dubbo通讯。
(2)需要多次重试或人工介入的过程。消息的另一个方便之处就是可以通过控制台手动发起调用。比如业务系统之间的数据同步,有可能出现同步方手动改库修改数据的问题,或者正常同步时网络问题导致同步只成功了一部分的情况,这时候修复数据不可能把整个流程重新走一遍(同步之前的流程可能包括配置、审核等等复杂步骤),这个时候在编码的时候就可以把同步流程独立出来使用消息触发,在出现问题的时候可以使用消息来单独把同步流程走一遍。
(3)异步。消息天然就是异步执行的。
公司内部对RocketMQ做了简单封装,将常用配置暴露出来可以通过spring配置文件进行配置:
1 <!-- 消费者 --> 2 <bean id="xxConsumer" class="com.cyan.mq.client.NotifyManagerBean" init-method="init"> 3 <property name="groupId" value="XXGROUP_C_PROJECTNAME" /> 4 <property name="topic" value="TOPICNAME"/> 5 <!-- 服务端主动给客户端推送数据 --> 6 <property name="ctype" value="PUSH"/> 7 <!-- 订阅多个tag --> 8 <property name="tag" value="tagName||tagName2" /> 9 <property name="namesrvAddr" value="${rocket.mq.namesrvAddr}"/> 10 <!-- 实现MessageListenerConcurrently接口的监听类 --> 11 <property name="messageListener" ref="xxListener"/> 12 <!-- 消费消息之后的回调类 --> 13 <property name="consumeMessageHook" ref="consumptionRecorder" /> 14 </bean> 15 16 17 <!-- 发起者 --> 18 <bean id="xxProducer" class="com.cyan.mq.client.NotifyManagerBean" init-method="initProducer"> 19 <property name="groupId" value="XXGROUP_P_PROJECTNAME" /> 20 <property name="name" value="producerName" /> 21 <property name="topic" value="TOPICNAME" /> 22 <property name="namesrvAddr" value="${rocket.mq.namesrvAddr}" /> 23 </bean>
其中groupId这个字段的命名不需要两边一样,一般在发起者(producer)的命名中加个P,在消费者(consumer)的命名中加个C。
消费者代码实现:
1 @Component 2 public class XxListener implements MessageListenerConcurrently { 3 private static final Logger logger = LoggerFactory.getLogger(XxListener.class); 4 @Override 5 public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) { 6 MessageExt messageExt = list.get(0); 7 8 String content = new String(messageExt.getBody()); 9 String tag = messageExt.getTags(); 10 logger.info("接收到消息,tag=" + tag + ",content=" + content + ", msgId=" + messageExt.getMsgId()); 11 // 具体逻辑 12 boolean success = handleMessages(messageExt); 13 if (!success) { 14 logger.error("消息消费失败, megId = {}", messageExt.getMsgId()); 15 return ConsumeConcurrentlyStatus.RECONSUME_LATER; 16 } 17 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; 18 } 19 }
生产者实现:
1 @Component 2 public class NormalServiceClass{ 3 private static final Logger logger = LoggerFactory.getLogger(XxListener.class); 4 5 @Resource(name="xxProducer") 6 private NotifyManagerBean customizedBean; 7 8 public void normalProcess(){ 9 // 业务逻辑 10 process(); 11 12 // 消息体 13 Message message = new BytesMessage(JSON.toJSONString(messageBody).getBytes(Charset.forName("UTF-8"))); 14 15 //发送mq消息 16 SendResult sendResult = customizedBean.sendMessage(message); 17 if(sendResult == null || sendResult.getSendStatus() != SendStatus.SEND_OK){ 18 logger.error("发送消息失败"); 19 }else{ 20 logger.info("发送消息成功"); 21 } 22 23 //业务逻辑 24 process(); 25 } 26 }
在使用过程中同样出现过本地服务注册到测试环境导致影响别人测试流程的问题,可行的方法是更改namesrv地址。不知道是否有更好的实践方法。
3、Spring
spring的IoC特性应该是它能成为Java web生态圈重要一环的主要原因。
公司中使用的spring文件命名规则主要是:
(1)projectname-context.xml 主要的上下文配置,引入下面说到的其他配置,velocity和线程池的配置(无法分类到下面其他文件里的配置)主要放在这里。
(2)projectname-disconf.xml 接入分布式配置中心disconf平台的配置内容。
(3)projectname-dubbo.xml dubbo相关的配置,暴露和引用的服务。
(4)projectname-jedis.xml redis相关配置。
(5)projectname-mq.xml RocketMQ相关配置,生产者和消费者都配置在此。
某个项目的配置:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <context:component-scan base-package="com.cyan.myproject" /> <!-- 启用aop --> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- velocityConfig --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/velocity/" /> <property name="velocityProperties"> <props> <prop key="input.encoding">UTF-8</prop> <prop key="output.encoding">UTF-8</prop> <prop key="directive.foreach.counter.name">loopCounter</prop> <prop key="directive.foreach.counter.initial.value">0</prop> </props> </property> </bean> <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="5" /> <property name="maxPoolSize" value="15" /> <property name="queueCapacity" value="2000" /> </bean> <import resource="classpath:META-INF/spring/myproject-dubbo.xml" /> <import resource="classpath:META-INF/spring/myproject-jedis.xml" /> <import resource="classpath:META-INF/spring/myproject-mq.xml" /> <!-- 引用三方包中的配置文件 --> <!-- disconf --> <import resource="classpath:META-INF/spring/myproject-disconf.xml" /> </beans>
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 如果已在spring-context.xml配置则不需再次重复配置 --> <!-- <aop:aspectj-autoproxy proxy-target-class="true"/>--> <!-- 使用disconf必须添加以下配置 --> <bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean" destroy-method="destroy"> <!-- scanPackage 的value必须为 相应的包路径 --> <property name="scanPackage" value="com.cyan.myproject"/> </bean> <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond" init-method="init" destroy-method="destroy"> </bean> <!-- 使用托管方式的disconf配置(无代码侵入, 配置更改不会自动reload)--> <bean id="configproperties_no_reloadable_disconf" class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean"> <property name="locations"> <!-- 此处包含除了disconf.properties 以外的所有配置文件 --> <list> <value>classpath:config.properties</value> <value>classpath:dubbo.properties</value> <value>classpath:jedis.properties</value> <value>classpath:log4j.properties</value> </list> </property> </bean> <bean id="propertyConfigurerForProject1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreResourceNotFound" value="true"/> <property name="ignoreUnresolvablePlaceholders" value="true"/> <property name="propertiesArray"> <list> <ref bean="configproperties_no_reloadable_disconf"/> </list> </property> </bean> </beans>
<?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:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://code.alibabatech.com/schema/dubbo"> <dubbo:application name="myproject"/> <!--zookeeper --> <dubbo:registry server="zkRegistry" protocol="${dubbo.registry.protocol}" address="${dubbo.registry.address}" file="${dubbo.registry.file} " /> <!-- <dubbo:monitor address="${dubbo.monitor.address}" /> --> <dubbo:protocol port="${dubbo.protocol.port}" /> <dubbo:provider timeout="${dubbo.provider.timeout}" retries="0"/> <dubbo:consumer check="false" /> <!-- ********* reference service ********* --> <dubbo:reference id="xxFacade" interface="com.cyan.otherproject.facade.xxFacade" version="${otherproject.dubbo.version}"></dubbo:reference> <!-- ********* publish service ********* --> <dubbo:service interface="com.cyan.myproject.facade.aaFacade" ref="aaFacadeImpl" version="2.0"/> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg ref="jedisPoolConfig" index="0" /> <constructor-arg index="1" value = "${jedis.pool.server1.ip}" /> <constructor-arg index="2" value = "${jedis.pool.server1.port}" /> </bean> <bean id="jedisPoolConfig" class = "redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${jedis.pool.maxIdle}" /> <property name="testOnBorrow" value="${jedis.pool.testOnBorrow}" /> <property name="testOnReturn" value="${jedis.pool.testOnReturn}" /> </bean> <bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool"> <constructor-arg index="0" ref="jedisPoolConfig" /> <constructor-arg index="1"> <list> <bean class="redis.clients.jedis.JedisShardInfo"> <constructor-arg index="0" value="${jedis.pool.server1.ip}" /> <constructor-arg index="1" value="${jedis.pool.server1.port}" type="int" /> <constructor-arg index="2" value="500" /> </bean> </list> </constructor-arg> </bean> <bean id="redisComponent" class="com.cyan.commons.component.RedisComponent" destroy-method="destory"> <property name="pool" ref="shardedJedisPool"></property> </bean> </beans>
<?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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd "> <bean id="consumerBean" class="com.cyan.mq.client.NotifyManagerBean" init-method="init"> <property name="groupId" value="MYPROJECT_C_GROUP" /> <property name="name" value="myProjectConsumer" /> <property name="topic" value="MYPROJECT_CUSTOMIZED_TOPIC"/> <property name="ctype" value="PUSH"/> <property name="namesrvAddr" value="${rocket.mq.namesrvAddr}"/> <property name="messageListener" ref="myListener"/> </bean> <bean id="produceBean" class="com.cyan.mq.client.NotifyManagerBean" init-method="initProducer"> <property name="groupId" value="MYPROJECT_P_GROUP" /> <property name="name" value="myProjectProducer" /> <property name="topic" value="MYPROJECT_CUSTOMIZED_TOPIC"/> <property name="namesrvAddr" value="${rocket.mq.namesrvAddr}"/> </bean> </beans>
aop相关的应用,日常很少去用,看到基本上用在需要入数据库记录日志的场景下。
1 @Component 2 @Aspect 3 public class LogComponent { 4 /*** 5 * 更新方法之后 6 */ 7 @After("execution(* com.cyan.myproject.component.ProductComponent.configProductByUpdate(..))") 8 public void updateProductAfter(JoinPoint joinPoint){ 9 ProduceObject product = null; 10 Object[] objs = joinPoint.getArgs(); 11 for(Object obj : objs){ 12 if(obj instanceof ProduceObject){ 13 product = (ProduceObject) obj; 14 break; 15 } 16 } 17 if(product != null){ 18 // addProductLog 19 } 20 } 21 22 /** 23 * 新增方法之后 24 */ 25 @After("execution(* com.cyan.myproject.component.ProductComponent.configProductBySave(..))") 26 public void addProductAfter(JoinPoint joinPoint){ 27 // addProductLog 28 } 29 }
4、MyBatis
5、公共组件
6、运维管理
7、Docker
二、工作流
1、项目开发流程
2、发布流程
三、其他
1、人际交往
沟通很重要,沟通很重要,沟通很重要。
因为你的主管并不是你妈,而且团队中也不只你一个人,主管不可能方方面面顾全你,所以你觉得工作不顺心了或者想涨工资了,必须要明确地去和上级沟通,不要觉得工资这个话题太敏感不好意思说,大家都是出来混口饭吃的,想涨薪是人之常情,如果你干的够好当然会给你应有的报酬,假如期望达不到,离职也不会是个坏决定。
多和同事们一起吃饭。工作时间大家坐在自己工位上各干各的,大家在合作中可以了解到你的工作性格,但是很难了解到你生活中的性格是怎样的,一起吃饭的时候会更多地聊到工作之外的话题,可以更快的和同事们打成一片,和合得来的人一起工作会更开心。
团建不仅仅是出去玩。团建其实更应该把它看成一个social场合,相比一起吃饭,一起玩可以更快的熟络起来。